时区结果因 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

请注意 bc 的值 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 TIMEZONETIMESTAMP WITH LOCAL TIMEZONE 而不是简单的 TIMEZONE...

不是完全合格的答案,但这里有一些信息:

数据类型TIMESTAMP不存储任何时区信息。当您插入带时区的时间戳(例如 SYSTIMESTAMP)时,时区信息将被截断。

SYSTIMESTAMP returns TIMESTAMP WITH TIME ZONE 数据库服务器操作系统时区时区的数据类型(UTC你的情况)

每当您将 convert/cast TIMESTAMP(无时区)更改为 TIMESTAMP WITH TIME ZONE 时,Oracle 都会考虑 SESSIONTIMEZONE - 除非您使用 [= 显式设置时区18=] 或类似的。

我认为您看到了两件事;当您以拉取、格式化和显示 bc 值的方式进行调试查询时,会导致 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() functiondatetime_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 的会话时区应用程序(基于其语言环境)导致了另一种方式的转变。