时区结果因 systimestamp 而异
Time zone results differ with systimestamp
我有一个 Oracle table,其中包含以下列定义:
COLUMN_NAME : RESERVATIONDATE
DATA_TYPE : TIMESTAMP(6)
NULLABLE : Yes
DATA_DEFAULT: null
然后,从 Java 我执行以下命令:
insert into my_table (col1, col2, reservationdate) values (:np1, :np2, systimestamp)
然后我在其他地方执行以下命令,目的是 return 行添加不到 X 秒前。
select * from my_table where reservationDate >= systimestamp - NUMTODSINTERVAL( :seconds, 'SECOND' )
但没有一行被 return 编辑,尽管预订日期晚于确定的时间点。
因此,我还运行来自同一应用程序的以下命令:
select col1,
col2,
reservationdate,
systimestamp as b,
systimestamp - NUMTODSINTERVAL( 5, 'SECOND' ) as c
from my_table
给出以下输出:
col1: value1
col2: value2
reservationdate:2017-06-14 14:31:00.746173
b :2017-06-14 15:31:00.905617
c :2017-06-14 15:30:55.905617
请注意 b
和 c
的值 return 基本上比 reservationdate
提前一小时。
运行 SQL 开发人员 运行 与 Java 应用程序在同一台机器上发出的相同请求给出了正确的值:
reservationdate:14-JUN-17 02.31.00.746173000 PM
b :14-JUN-17 02.58.32.863300000 PM +00:00
c :14-JUN-17 02.58.27.863300000 PM +00:00
Oracle 在一台虚拟机上 运行ning,其中 Unix 日期的输出为:
Wed Jun 14 14:18:11 UTC 2017
并且在 Java 所在的机器上 运行ning:
Wed Jun 14 15:21:24 BST 2017
显然这是一个时区问题,但我不明白它的确切来源。毕竟我一直在使用 systimestamp
,目的是在数据库服务器上进行所有时间戳计算。
我的请求通过 Spring 的 NamedParameterJdbcTemplate
,我使用的是 Oracle 数据库 11g 快捷版 11.2.0.2.0
。
尝试 TIMESTAMP WITH TIMEZONE
或 TIMESTAMP WITH LOCAL TIMEZONE
而不是简单的 TIMEZONE
...
不是完全合格的答案,但这里有一些信息:
数据类型TIMESTAMP
不存储任何时区信息。当您插入带时区的时间戳(例如 SYSTIMESTAMP
)时,时区信息将被截断。
SYSTIMESTAMP
returns TIMESTAMP WITH TIME ZONE
数据库服务器操作系统时区时区的数据类型(UTC
你的情况)
每当您将 convert/cast TIMESTAMP
(无时区)更改为 TIMESTAMP WITH TIME ZONE
时,Oracle 都会考虑 SESSIONTIMEZONE
- 除非您使用 [= 显式设置时区18=] 或类似的。
我认为您看到了两件事;当您以拉取、格式化和显示 b
和 c
值的方式进行调试查询时,会导致 Java 转换时区。但我认为这是一个转移注意力的问题,因为这不会直接影响您查找最近更改的行的原始查询。它可能与您的 Java 语言环境有关,因此与主要查询问题有关。
如果您跟踪最初 运行ning 的查询,您应该会看到它实际使用的过滤器是:
SYS_EXTRACT_UTC(INTERNAL_FUNCTION(RESERVATIONDATE))
>=SYS_EXTRACT_UTC(SYSTIMESTAMP(6)-NUMTODSINTERVAL(TO_NUMBER(:SECONDS),'SECOND'))
有函数调用是因为 "During SELECT FROM operations, Oracle converts the data from the column to the type of the target variable.";因此列值被转换为带有时区的时间戳,以便与 systimestamp
进行比较,然后将两个时间戳与区域值进行比较,它们都被转换为 UTC。
然后这又回到了@WernfriedDomscheit 所说的。 sys_extract_utc()
function 将 datetime_with_timezone
值作为其参数,因此当传入普通 timestamp
时,它会使用 SESSIONTIMEZONE
隐式转换。您看到的查询结果不同,因为您的 Java 代码和 SQL 开发人员之间的会话时区不同。
您可以在两者中 select sessiontimezone from dual
查看它们是什么,或者查看时间戳实际上是如何转换的。
您可以通过将系统时间转换回无时区时间戳来避免此问题:
... where reservationDate >= cast(systimestamp as timestamp) - NUMTODSINTERVAL( :seconds, 'SECOND' );
显示更简单的跟踪:
RESERVATIONDATE
>=CAST(SYSTIMESTAMP(6) AS timestamp)-NUMTODSINTERVAL(TO_NUMBER(:SECONDS),'SECOND')
将您的列定义从 timestamp
更改为 timestamp with [local] time zone
也可以; local
到 UTC 的转换仍然发生,根据我的踪迹,没有本地它不会。 This is an interesting read too.
如果您查看 sys_extract_utc()
呼叫如何使用不同的会话时区进行解析,您会发现差异,使用新插入的行并为简洁起见省略日期元素:
insert into my_table (col1, col2, reservationdate) values (:np1, :np2, systimestamp);
alter session set nls_timestamp_format = 'HH24:MI:SS.FF1';
alter session set nls_timestamp_tz_format = 'HH24:MI:SS.FF1 TZR';
alter session set time_zone = 'Europe/London';
select reservationdate a,
systimestamp b,
systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND') c,
cast(reservationdate as timestamp with time zone) d,
sys_extract_utc(reservationdate) e,
sys_extract_utc(systimestamp) f,
sys_extract_utc(systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND')) g
from my_table;
A B C D E F G
----------- ----------------- ----------------- ------------------------ ----------- ----------- -----------
17:12:13.9 17:12:15.0 +01:00 17:12:10.0 +01:00 17:12:13.9 EUROPE/LONDON 16:12:13.9 16:12:15.0 16:12:10.0
alter session set time_zone = 'UTC';
select reservationdate a,
systimestamp(6) b,
systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND') c,
cast(reservationdate as timestamp with time zone) d,
sys_extract_utc(reservationdate) e,
sys_extract_utc(systimestamp) f,
sys_extract_utc(systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND')) g
from my_table;
A B C D E F G
----------- ----------------- ----------------- -------------- ----------- ----------- -----------
17:12:13.9 17:12:15.4 +01:00 17:12:10.4 +01:00 17:12:13.9 UTC 17:12:13.9 16:12:15.4 16:12:10.4
我的设置似乎与您的不同 - 我的数据库服务器使用英国时间,但 DBTIMEZONE
是 +00:00 - 但即便如此,请注意 e
栏目。在我的例子中,它看起来会找到太多而不是太少的数据——因为 e >= g
是正确的。在你的情况下,我相信如果你 运行 来自 SQL Developer 和 Java 的人,你会看到相反的差异,因为你的 Java 的会话时区应用程序(基于其语言环境)导致了另一种方式的转变。
我有一个 Oracle table,其中包含以下列定义:
COLUMN_NAME : RESERVATIONDATE
DATA_TYPE : TIMESTAMP(6)
NULLABLE : Yes
DATA_DEFAULT: null
然后,从 Java 我执行以下命令:
insert into my_table (col1, col2, reservationdate) values (:np1, :np2, systimestamp)
然后我在其他地方执行以下命令,目的是 return 行添加不到 X 秒前。
select * from my_table where reservationDate >= systimestamp - NUMTODSINTERVAL( :seconds, 'SECOND' )
但没有一行被 return 编辑,尽管预订日期晚于确定的时间点。
因此,我还运行来自同一应用程序的以下命令:
select col1,
col2,
reservationdate,
systimestamp as b,
systimestamp - NUMTODSINTERVAL( 5, 'SECOND' ) as c
from my_table
给出以下输出:
col1: value1
col2: value2
reservationdate:2017-06-14 14:31:00.746173
b :2017-06-14 15:31:00.905617
c :2017-06-14 15:30:55.905617
请注意 b
和 c
的值 return 基本上比 reservationdate
提前一小时。
运行 SQL 开发人员 运行 与 Java 应用程序在同一台机器上发出的相同请求给出了正确的值:
reservationdate:14-JUN-17 02.31.00.746173000 PM
b :14-JUN-17 02.58.32.863300000 PM +00:00
c :14-JUN-17 02.58.27.863300000 PM +00:00
Oracle 在一台虚拟机上 运行ning,其中 Unix 日期的输出为:
Wed Jun 14 14:18:11 UTC 2017
并且在 Java 所在的机器上 运行ning:
Wed Jun 14 15:21:24 BST 2017
显然这是一个时区问题,但我不明白它的确切来源。毕竟我一直在使用 systimestamp
,目的是在数据库服务器上进行所有时间戳计算。
我的请求通过 Spring 的 NamedParameterJdbcTemplate
,我使用的是 Oracle 数据库 11g 快捷版 11.2.0.2.0
。
尝试 TIMESTAMP WITH TIMEZONE
或 TIMESTAMP WITH LOCAL TIMEZONE
而不是简单的 TIMEZONE
...
不是完全合格的答案,但这里有一些信息:
数据类型TIMESTAMP
不存储任何时区信息。当您插入带时区的时间戳(例如 SYSTIMESTAMP
)时,时区信息将被截断。
SYSTIMESTAMP
returns TIMESTAMP WITH TIME ZONE
数据库服务器操作系统时区时区的数据类型(UTC
你的情况)
每当您将 convert/cast TIMESTAMP
(无时区)更改为 TIMESTAMP WITH TIME ZONE
时,Oracle 都会考虑 SESSIONTIMEZONE
- 除非您使用 [= 显式设置时区18=] 或类似的。
我认为您看到了两件事;当您以拉取、格式化和显示 b
和 c
值的方式进行调试查询时,会导致 Java 转换时区。但我认为这是一个转移注意力的问题,因为这不会直接影响您查找最近更改的行的原始查询。它可能与您的 Java 语言环境有关,因此与主要查询问题有关。
如果您跟踪最初 运行ning 的查询,您应该会看到它实际使用的过滤器是:
SYS_EXTRACT_UTC(INTERNAL_FUNCTION(RESERVATIONDATE))
>=SYS_EXTRACT_UTC(SYSTIMESTAMP(6)-NUMTODSINTERVAL(TO_NUMBER(:SECONDS),'SECOND'))
有函数调用是因为 "During SELECT FROM operations, Oracle converts the data from the column to the type of the target variable.";因此列值被转换为带有时区的时间戳,以便与 systimestamp
进行比较,然后将两个时间戳与区域值进行比较,它们都被转换为 UTC。
然后这又回到了@WernfriedDomscheit 所说的。 sys_extract_utc()
function 将 datetime_with_timezone
值作为其参数,因此当传入普通 timestamp
时,它会使用 SESSIONTIMEZONE
隐式转换。您看到的查询结果不同,因为您的 Java 代码和 SQL 开发人员之间的会话时区不同。
您可以在两者中 select sessiontimezone from dual
查看它们是什么,或者查看时间戳实际上是如何转换的。
您可以通过将系统时间转换回无时区时间戳来避免此问题:
... where reservationDate >= cast(systimestamp as timestamp) - NUMTODSINTERVAL( :seconds, 'SECOND' );
显示更简单的跟踪:
RESERVATIONDATE
>=CAST(SYSTIMESTAMP(6) AS timestamp)-NUMTODSINTERVAL(TO_NUMBER(:SECONDS),'SECOND')
将您的列定义从 timestamp
更改为 timestamp with [local] time zone
也可以; local
到 UTC 的转换仍然发生,根据我的踪迹,没有本地它不会。 This is an interesting read too.
如果您查看 sys_extract_utc()
呼叫如何使用不同的会话时区进行解析,您会发现差异,使用新插入的行并为简洁起见省略日期元素:
insert into my_table (col1, col2, reservationdate) values (:np1, :np2, systimestamp);
alter session set nls_timestamp_format = 'HH24:MI:SS.FF1';
alter session set nls_timestamp_tz_format = 'HH24:MI:SS.FF1 TZR';
alter session set time_zone = 'Europe/London';
select reservationdate a,
systimestamp b,
systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND') c,
cast(reservationdate as timestamp with time zone) d,
sys_extract_utc(reservationdate) e,
sys_extract_utc(systimestamp) f,
sys_extract_utc(systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND')) g
from my_table;
A B C D E F G
----------- ----------------- ----------------- ------------------------ ----------- ----------- -----------
17:12:13.9 17:12:15.0 +01:00 17:12:10.0 +01:00 17:12:13.9 EUROPE/LONDON 16:12:13.9 16:12:15.0 16:12:10.0
alter session set time_zone = 'UTC';
select reservationdate a,
systimestamp(6) b,
systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND') c,
cast(reservationdate as timestamp with time zone) d,
sys_extract_utc(reservationdate) e,
sys_extract_utc(systimestamp) f,
sys_extract_utc(systimestamp(6)-numtodsinterval(to_number(:seconds),'SECOND')) g
from my_table;
A B C D E F G
----------- ----------------- ----------------- -------------- ----------- ----------- -----------
17:12:13.9 17:12:15.4 +01:00 17:12:10.4 +01:00 17:12:13.9 UTC 17:12:13.9 16:12:15.4 16:12:10.4
我的设置似乎与您的不同 - 我的数据库服务器使用英国时间,但 DBTIMEZONE
是 +00:00 - 但即便如此,请注意 e
栏目。在我的例子中,它看起来会找到太多而不是太少的数据——因为 e >= g
是正确的。在你的情况下,我相信如果你 运行 来自 SQL Developer 和 Java 的人,你会看到相反的差异,因为你的 Java 的会话时区应用程序(基于其语言环境)导致了另一种方式的转变。