从"使用本地时区的时间戳"列中检索数据时,时区何时调整为用户的时区?



根据 Oracle 文档,带有本地时区的时间戳行为如下。

  1. 存储在数据库中的数据将规范化为数据库时区,并且时区信息不存储为列数据的一部分。
  2. 当用户检索数据时,Oracle 会以用户的本地会话时区返回数据。

我有一个关于第二点的问题,即检索。

也就是说,这种到用户时区的转换是由数据库本身发生的,还是数据库客户端/驱动程序负责执行此操作?

例如:假设我正在使用Oracle JDBC驱动程序。驱动程序是将时间值转换为用户的时区,还是数据库应返回用户时区中的时间值?如果数据库应该进行转换,则客户端应该向数据库提供其时区的指示,不是吗?

数据库会这样做。如果将相同的标称时刻存储在时间戳中,时间戳与时区和时间戳与本地时区列:

create table t42 (id number,
ts timestamp,
tstz timestamp with time zone,
tsltz timestamp with local time zone
);
insert into t42 (id, ts, tstz, tsltz)
values (1,
timestamp '2019-08-13 07:13:20 UTC',
timestamp '2019-08-13 07:13:20 UTC',
timestamp '2019-08-13 07:13:20 UTC'
);
insert into t42 (id, ts, tstz, tsltz)
values (2,
timestamp '2019-08-13 12:34:56.789 Australia/Sydney',
timestamp '2019-08-13 12:34:56.789 Australia/Sydney',
timestamp '2019-08-13 12:34:56.789 Australia/Sydney'
);
alter session set time_zone = 'Europe/London';
select id, ts, tstz, tsltz from t42 order by id;
ID TS                      TSTZ                                     TSLTZ                  
-- ----------------------- ---------------------------------------- -----------------------
1 2019-08-13 07:13:20.000 2019-08-13 07:13:20.000 UTC              2019-08-13 03:13:20.000
2 2019-08-13 12:34:56.789 2019-08-13 12:34:56.789 AUSTRALIA/SYDNEY 2019-08-12 22:34:56.789

。您可以使用dump()来查看它们在内部的实际存储方式:

select id, 'TS' as col, to_char(ts, 'YYYY-MM-DD HH24:MI:SS.FF3') as value, dump(ts) as dumped from t42
union all
select id, 'TSTZ', to_char(tstz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tstz) from t42
union all
select id, 'TSLTZ', to_char(tsltz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tsltz) from t42
order by 1, 4;
ID COL   VALUE                                    DUMPED                                                 
-- ----- ---------------------------------------- -------------------------------------------------------
1 TS    2019-08-13 07:13:20.000                  Typ=180 Len=7: 120,119,8,13,8,14,21                    
1 TSTZ  2019-08-13 07:13:20.000 UTC              Typ=181 Len=13: 120,119,8,13,8,14,21,0,0,0,0,208,4     
1 TSLTZ 2019-08-13 08:13:20.000 EUROPE/LONDON    Typ=231 Len=7: 120,119,8,13,8,14,21                    
2 TS    2019-08-13 12:34:56.789                  Typ=180 Len=11: 120,119,8,13,13,35,57,47,7,47,64       
2 TSTZ  2019-08-13 12:34:56.789 AUSTRALIA/SYDNEY Typ=181 Len=13: 120,119,8,13,3,35,57,47,7,47,64,133,128
2 TSLTZ 2019-08-13 03:34:56.789 EUROPE/LONDON    Typ=231 Len=11: 120,119,8,13,3,35,57,47,7,47,64        

对于原始 UTC 值,您可以看到 TS 和 TSLTZ 值以完全相同的字节存储,但类型代码不同;而 TSTZ 是第三种类型,前 7 个字节相同(存储的日期/时间 - 请参阅 MoS Doc ID 69028.1,或此处 - 这些字节与 type-12 日期相同)加上时区信息的其他字节(此处为零表示小数秒)。日期/时间字节都相同,因为它们都是 UTC。无论如何,在我的数据库中,因为这是我的 DBTIMEZONE:

select dbtimezone, sessiontimezone from dual;
DBTIME SESSIONTIMEZONE                                                            
------ ---------------------------------------------------------------------------
+00:00 Europe/London                                                              

对于原始悉尼值,TS 值是本地时间,因此小时字节为 13 (12+1); 对于 TSTZ 和 TSLTZ,日期/时间字节仍然是 UTC,因此它们有 3 (2+1);TSTZ也有时区信息。TSLTZ 没有时区信息,因为类型 231 始终是 UTC。当它转换为字符串时,将应用会话时区,因此存储的小时字节 3 (2+1) 变为本地小时 3。

在不同的会话时区中,您会看到大致相同的内容:

alter session set time_zone = 'America/New_York';
select id, 'TS' as col, to_char(ts, 'YYYY-MM-DD HH24:MI:SS.FF3') as value, dump(ts) as dumped from t42
union all
select id, 'TSTZ', to_char(tstz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tstz) from t42
union all
select id, 'TSLTZ', to_char(tsltz, 'YYYY-MM-DD HH24:MI:SS.FF3 TZR'), dump(tsltz) from t42
order by 1, 4;
ID COL   VALUE                                    DUMPED                                                 
-- ----- ---------------------------------------- -------------------------------------------------------
1 TS    2019-08-13 07:13:20.000                  Typ=180 Len=7: 120,119,8,13,8,14,21                    
1 TSTZ  2019-08-13 07:13:20.000 UTC              Typ=181 Len=13: 120,119,8,13,8,14,21,0,0,0,0,208,4     
1 TSLTZ 2019-08-13 03:13:20.000 AMERICA/NEW_YORK Typ=231 Len=7: 120,119,8,13,8,14,21                    
2 TS    2019-08-13 12:34:56.789                  Typ=180 Len=11: 120,119,8,13,13,35,57,47,7,47,64       
2 TSTZ  2019-08-13 12:34:56.789 AUSTRALIA/SYDNEY Typ=181 Len=13: 120,119,8,13,3,35,57,47,7,47,64,133,128
2 TSLTZ 2019-08-12 22:34:56.789 AMERICA/NEW_YORK Typ=231 Len=11: 120,119,8,13,3,35,57,47,7,47,64        

当然,存储的字节是完全相同的;但现在TSLTZ值被调整为纽约时间,所以对于悉尼,小时字节3(2+1)变成了本地小时22,并且日期不同。

(由于我格式化它的方式,这里 TSLTZ 与会话时区一起显示;正如您在第一个查询中看到的那样,它实际上没有时区,因此正在进行进一步的隐式转换以显示该会话值)。

如果让客户端格式化底层内部字节结构,则会看到表示的相同值,就像我在上面的第一个查询中所做的那样。但是使用to_char()回答了您问题的主要部分;在将值从其内部格式转换为字符串之前,数据库必须按原样执行此操作。如果客户端(或 JDBC)对本地时间执行该调整,则to_char()将只能看到存储的基于 DB-时区的字节,并且始终为您提供与时间等效的 DBTIMEZONE。

您的客户端(或 JDBC)正在告诉数据库要使用哪个本地时区。我一直在用alter session覆盖它,你可以通过JDBC做同样的事情。默认情况下,它基于您的 Java 语言环境和其他应用程序设置;但你可以覆盖它。

最新更新