因最近国际去Oracle上MySQL,这就不可避免的涉及到时区和timestamp问题。做一下实验,总结一下。
Oracle
首先看下oracle concepts对timestamp的定义:
The TIMESTAMP data type is an extension of the DATE
data type. It stores fractional seconds in addition to the information stored in the DATE
data type. TheTIMESTAMP
data type is useful for storing precise time values, such as in applications that must track event order.
TIMESTAMP
WITH
TIME
ZONE
is a variant of TIMESTAMP
that includes a time zone region name or a time zone offset in its value. The time zone offset is the difference (in hours and minutes) between local time and UTC (Coordinated Universal Time—formerly Greenwich Mean Time). This data type is useful for preserving local time zone information.
TIMESTAMP
WITH
LOCAL
TIME
ZONE
is another variant of TIMESTAMP
that is sensitive to time zone information. It differs from TIMESTAMP
WITH
TIME
ZONE
in that data stored in the database is normalized to the database time zone, and the time zone information is not stored as part of the column data. When a user retrieves the data, Oracle returns it in the user‘s local session time zone. This data type is useful for date information that is always to be displayed in the time zone of the client system in a two-tier application.
- TIMESTAMP是对date的更高精度的一种存储,但它不存储时区信息,即不受DBTIMEZONE影响
- TIMESTAMP WITH TIME ZONE存储客户端的时区信息,所以也不受DBTIMEZONE影响
- TIMESTAMP WITH LOCAL TIME ZONE类型数据不会存储客户端的时区信息,它根据数据库时区对客户端发来的时间进行转换,基于统一的数据库时区存储时间信息,如果用户没有指定时区信息同TIMESTAMP WITH TIME ZONE一样默认采用会话时区。把客户端输入的时间转换为基于database timezone的时间后存入数据库(这也就是database tmiezone设置的意义所在,作为TIMESTAMP WITH LOCAL TIME ZONE类型的计算标尺)。当用户查看该类型数据时,服务器根据会话所属时区对存储的时间数据进行转换,不同时区的会话将返回不同的时间数据。所以Oracle建议把database timezone设置为标准时间UTC,这样可以节省每次转换所需要的开销,提高性能。
v$nls_parameters表不仅存了数据库的字符集信息,还有关于timestamp和timestamp with local time zone的显示格式:
SQL> select * from v$nls_parameters where parameter in (‘NLS_TIMESTAMP_FORMAT‘,‘NLS_TIMESTAMP_TZ_FORMAT‘);
PARAMETER VALUE
------------------------------ ------------------------------
NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR
看完定义,我们直接用实验更直观地看出它们的不同。
$ date -R
Sun, 24 Apr 2016 13:50:32 +0800
SQL>select dbtimezone,sessiontimezone from dual;
DBTIMEZONE SESSIONTIMEZONE
-------------------- --------------------
+08:00 +08:00
我们进行插入数据的实验
SQL>create table timezone_test(t0 timestamp,t1 timestamp with time zone,t2 timestamp with local time zone);
Table created.
SQL>insert into timezone_test select current_timestamp,current_timestamp,current_timestamp from dual;
1 row created.
SQL>select * from timezone_test;
T0 T1 T2
------------------------------ ------------------------------------ ------------------------------
24-APR-16 02.35.03.613433 PM 24-APR-16 02.35.03.613433 PM +08:00 24-APR-16 02.35.03.613433 PM
SQL>alter session set time_zone=‘-2:00‘;
Session altered.
SQL>select * from timezone_test;
T0 T1 T2
------------------------------ ------------------------------------ ------------------------------
24-APR-16 02.35.03.613433 PM 24-APR-16 02.35.03.613433 PM +08:00 24-APR-16 04.35.03.613433 AM
留意到T0和T1都是不变的。T2,即timestamp with local time zone所输出的值发生了变化。我们这时从-2:00的另一个db用sqlplus连接,效果也是一样的:
SQL>select dbtimezone,sessiontimezone from dual;
DBTIMEZONE SESSIONTIMEZONE
-------------------- --------------------
+08:00 -02:00
SQL>insert into timezone_test select current_timestamp,current_timestamp,current_timestamp from dual;
1 row created.
SQL>select * from timezone_test;
T0 T1 T2
------------------------------ ------------------------------------ ------------------------------
24-APR-16 02.35.03.613433 PM 24-APR-16 02.35.03.613433 PM +08:00 24-APR-16 04.35.03.613433 AM
24-APR-16 05.03.35.050304 AM 24-APR-16 05.03.35.050304 AM -02:00 24-APR-16 05.03.35.050304 AM
T2为(-2)-(+8)=-10时差
SQL>alter session set time_zone = dbtimezone;
Session altered.
SQL>select * from timezone_test;
T0 T1 T2
------------------------------ ------------------------------------ ------------------------------
24-APR-16 02.35.03.613433 PM 24-APR-16 02.35.03.613433 PM +08:00 24-APR-16 02.35.03.613433 PM
24-APR-16 05.03.35.050304 AM 24-APR-16 05.03.35.050304 AM -02:00 24-APR-16 03.03.35.050304 PM
当dbtimezone与sessiontimezone不同时,插入数据。T0和T1即保留了插入时的时间字符串信息,不会改变,而T2则回到了我们真正插入的时间,即下午3点03分。
SQL>select dbtimezone,sessiontimezone from dual;
DBTIMEZONE SESSIONTIMEZONE
-------------------- --------------------
+08:00 -02:00
SQL>insert into timezone_test select timestamp ‘2016-04-24 15:14:00 +3:00‘,timestamp ‘2016-04-24 15:14:00 +3:00‘,timestamp ‘2016-04-24 15:14:00 +3:00‘ from dual;
1 row created.
SQL>select * from timezone_test;
T0 T1 T2
------------------------------ ------------------------------------ ------------------------------
24-APR-16 02.35.03.613433 PM 24-APR-16 02.35.03.613433 PM +08:00 24-APR-16 02.35.03.613433 PM
24-APR-16 05.03.35.050304 AM 24-APR-16 05.03.35.050304 AM -02:00 24-APR-16 03.03.35.050304 PM
24-APR-16 03.14.00.000000 PM 24-APR-16 03.14.00.000000 PM +03:00 24-APR-16 08.14.00.000000 PM
我们留意到,T0和T1都是我们插入的timestamp字符串中的时间,而T2的时间则已经是(+3) - (-2) = 5,即已经进行了转化。
SQL>alter session set time_zone=‘+3:00‘;
Session altered.
SQL>select * from timezone_test;
T0 T1 T2
------------------------------ ------------------------------------ ------------------------------
24-APR-16 02.35.03.613433 PM 24-APR-16 02.35.03.613433 PM +08:00 24-APR-16 09.35.03.613433 AM
24-APR-16 05.03.35.050304 AM 24-APR-16 05.03.35.050304 AM -02:00 24-APR-16 10.03.35.050304 AM
24-APR-16 03.14.00.000000 PM 24-APR-16 03.14.00.000000 PM +03:00 24-APR-16 03.14.00.000000 PM
而我们把时区再设置成我们插入时指定的+3时区时,即显示出现的时间即是我们插入时的时间字符串的值了。
所以,在Oracle中,TIMESTAMP WITH LOCAL TIME ZONE会随着用户所在时区(SESSIONTIMEZONE)而变化,而TIMESTAMP WITH TIME ZONE则不随用户所在时区的变化而变,简单的说,这两个时间类型的参照时间不同,一个是参照用户的时区,一个是参照数据库的时区。
timestamp with time zone则要加上时区,插入数据时插的什么时区就显示什么时区,不会改变为别的或数据库所在时区,或查询人所在地的时区。插入数据时,如果写时区,那么显示的时候以插入时候的时区显示出来,而不是数据库所在时区,或查询人所在地的时区的时间;并且也不会转换这个时间。
而timestamp with local time zone 就是显示的时候不加后面的时区如+8:00。会存在转换的问题。插入数据的时候带时区就会转换会数据库所在时区,或查询人所在地的时区来显示数据。插入的时候不带时区,则认为跟数据库所在的时区是一样,这样查询时的所在地如果与数据库一样的时区,则时间不变,如果不一致,则还要转换为查询所在的时区的时间。
但是,如果建库时,时间设置错误或者是将来要改变时区时,表中的值会不会变化呢?由于我没测试环境,因此引用官方的回答:
对于time zone数据类型的数据,即使你更新了数据库时区,原数据也不会进行对应调整,只能你导出数据,然后调整数据库时区,再把原始数据导入即可。所以,一般情况下,一定不要调整数据库时区。官方建议数据库时间采用UTC,因为这种时区性能好。如果没有显式指定数据库时区,数据库会使用操作系统的时区,但是如果操作系统时区不是一个合理的数据库时区,数据库则会使用默认的时区UTC,且UTC的取值范围为-12:00 to +14:00。等等,为什么会有+14?百度一下基里巴斯。这是一个神奇的网站国度。
MySQL
看下MySQL 5.6 Reference Manual对timestamp的定义
The TIMESTAMP data type is used for values that contain both date and time parts. TIMESTAMP has a range of ‘1970-01-01 00:00:01‘ UTC to ‘2038-01-19 03:14:07‘ UTC.
MySQL converts TIMESTAMP values from the current time zone to UTC for storage, and back from UTC to the current time zone for retrieval. (This does not occur for other types such as DATETIME .) By default, the current time zone for each connection is the server‘s time. The time zone can be set on a per-connection basis. As long as the time zone setting remains constant, you get back the same value you store. If you store a TIMESTAMP value, and then change the time zone and retrieve the value, the retrieved value is different from the value you stored. This occurs because the same time zone was not used for conversion in both directions. The current time zone is available as the value of the time_zone system variable.
我们直接看下mysql中date和timestamp的不同:
DATETIME
1.8个字节储存(8 bytes storage)
2.实际格式储存(Just stores what you have stored and retrieves the same thing which you have stored.)
3.与时区无关(It has nothing to deal with the TIMEZONE and Conversion.)
TIMESTAMP
1.4个字节储存(Time stamp value is stored in 4 bytes)
2.值以UTC格式保存( it stores the number of milliseconds)
3.时区转化,存储时对当前的时区进行转换,检索时再转换回当前的时区。
mysql的timestamp没有oracle复杂,直接实验一下。
$date -R
Sun, 24 Apr 2016 05:50:02 -0700
[email protected] 05:50:16>show variables like ‘%time_zone%‘;
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | PDT |
| time_zone | SYSTEM |
+------------------+--------+
[email protected] 05:50:14>create table timezone_test (
-> t1 datetime default null,
-> t2 timestamp not null default current_timestamp on update current_timestamp);
[email protected] 05:52:11>select * from timezone_test;
+---------------------+---------------------+
| t1 | t2 |
+---------------------+---------------------+
| 2016-04-24 05:52:11 | 2016-04-24 05:52:11 |
+---------------------+---------------------+
[email protected] 05:52:21>set time_zone=‘+8:00‘;
[email protected] 05:52:47>select * from timezone_test;
+---------------------+---------------------+
| t1 | t2 |
+---------------------+---------------------+
| 2016-04-24 05:52:11 | 2016-04-24 20:52:11 |
+---------------------+---------------------+
[email protected] 05:52:53>insert into timezone_test values(current_timestamp,current_timestamp);
[email protected] 05:52:59>select * from timezone_test;
+---------------------+---------------------+
| t1 | t2 |
+---------------------+---------------------+
| 2016-04-24 05:52:11 | 2016-04-24 20:52:11 |
| 2016-04-24 20:55:04 | 2016-04-24 20:55:04 |
+---------------------+---------------------+
[email protected] 05:55:04>set time_zone=‘-7:00‘;
[email protected] 05:55:12>show variables like ‘%time_zone%‘;
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | PDT |
| time_zone | SYSTEM |
+------------------+--------+
[email protected] 05:55:22>select * from timezone_test;
+---------------------+---------------------+
| t1 | t2 |
+---------------------+---------------------+
| 2016-04-24 05:52:11 | 2016-04-24 05:52:11 |
| 2016-04-24 20:55:04 | 2016-04-24 05:55:04 |
+---------------------+---------------------+
而在刚才创建表时timestamp列时用的default值,有下面的结论,有兴趣可以做实验下:
- 在创建新记录和修改现有记录的时候都对这个数据列刷新:
TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- 在创建新记录的时候把这个字段设置为当前时间,但以后修改时,不再刷新它:
TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- 在创建新记录的时候把这个字段设置为0,以后修改时刷新它:
TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- 在创建新记录的时候把这个字段设置为给定值,以后修改时刷新它:
TIMESTAMP DEFAULT ‘yyyy-mm-dd hh:mm:ss‘ ON UPDATE CURRENT_TIMESTAMP
那比如原mysql的时区是-7:00,然后又新增一个读库,这个读库的时区反而是在+8:00,它的实际值会有不同么?我们来实验下:
源库:
$date -R
Tue, 26 Apr 2016 01:47:52 -0700
[email protected] 02:01:21>show variables like ‘%time_zone%‘;
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | PDT |
| time_zone | SYSTEM |
+------------------+--------+
[email protected] 02:01:25>create table timestamp_test(t0 timestamp);
[email protected] 02:01:31>insert into timestamp_test values(current_timestamp);
[email protected] 02:01:46>select * from timestamp_test;
+---------------------+
| t0 |
+---------------------+
| 2016-04-26 02:01:46 |
+---------------------+
新读库:
$date -R
Tue, 26 Apr 2016 16:52:39 +0800
[email protected] 05:02:54>show variables like ‘%time_zone%‘;
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | CST |
| time_zone | SYSTEM |
+------------------+--------+
[email protected] 05:03:00>select * from timestamp_test;
+---------------------+
| t0 |
+---------------------+
| 2016-04-26 17:01:46 |
+---------------------+
1 row in set (0.00 sec)
[email protected] 05:03:02>set time_zone=‘-7:00‘;
[email protected] 05:03:48>select * from timestamp_test;
+---------------------+
| t0 |
+---------------------+
| 2016-04-26 02:01:46 |
+---------------------+
因为mysql的主备同步,同步的还是sql,就算是row模式的同步也只是把其实同步的值以物理块的形式传输,实际还是要在目标端转换为sql来执行,仍然是带有时区信息,会在客户端进行时区转换。
总结一下:
1、Oracle和MySQL中的timestamp的作用是不同的
- Oracle中,TIMESTAMP是对date的更高精度的一种存储,是作为datetime的延展,但它不存储时区信息
- Oracle中,TIMESTAMP WITH TIME ZONE存储时区信息
- Oracle中,TIMESTAMP WITH LOCAL TIME ZONE不会存储时区信息,将时间数据转换为数据库时区的时间数据进行存储,但不存储时区信息;客户端检索时,oracle会将数据库中存储的时间数据转换为客户端session时区的时间数据后返回给客户端
- MYSQL中,的TIMESTAMP是为了更少的存储单元(DATETIME为4字节,TIMESTAMP为1个字节)但是范围为1970的某时的开始到2037年,而且会根据客户端的时区判断返回值,MYSQL的TIMESTAMP时区敏感这点和ORACLE的TIMESTAMP WITH LOCAL TIME ZONE一致。
2、ORACLE和MYSQL的函数返回不一样
- oracle读取的时区信息是以client端为准,CURRENT_TIMESTAMP都受到客户端SESSION TIMEZONE影响,而SYSDATE,SYSTIMESTAP不受影响
- mysql读取的时区信息是以server端为准,NOW(),SYSDATE(),CURRENT_TIMESTAMP 均不受到客户端连接时区影响
- DTS始终为client端,数据到达DTS时,都统一变成纯字符串
3、Oracle的DBTIMEZONE只和TIMESTAMP WITH LOCAL TIME ZONE有关。MySQL中的time_zone直接影响所有的timestamp取值。
4、为了返回一致的数据MYSQL设置TIME_ZONE参数即可,因为他是每个连接都会用到的,但是ORACLE最好使用SYSDATE或者SYSTIMESTAMP来直接取DB SERVER端时间。
5、MySQL修改时区信息,只要CLIENT端的时区信息不变,此无影响。
6、Oracle修改时间信息,同理,TIMESTAMP WITH LOCAL TIME ZONE不受影响,TIMESTAMP和TIMESTAMP WITH TIME ZONE会发生变化。
7、如果在client中不指定时区信息,oracle以client端的时区信息为准,要进行转换,mysql以server端的时区信息为准。