oracle、mysql時區設置對timestamp的不同影響


因最近國際去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

 

root@test 05:50:16>show variables like '%time_zone%';

+------------------+--------+

| Variable_name    | Value  |

+------------------+--------+

| system_time_zone | PDT    |

| time_zone        | SYSTEM |

+------------------+--------+

 

root@test 05:50:14>create table timezone_test (

    ->   t1 datetime default null,

    ->   t2 timestamp not null default current_timestamp on update current_timestamp);

 

root@test 05:52:11>select * from timezone_test;

+---------------------+---------------------+

| t1                  | t2                  |

+---------------------+---------------------+

| 2016-04-24 05:52:11 | 2016-04-24 05:52:11 |

+---------------------+---------------------+

 

root@test 05:52:21>set time_zone='+8:00';

 

root@test 05:52:47>select * from timezone_test;

+---------------------+---------------------+

| t1                  | t2                  |

+---------------------+---------------------+

| 2016-04-24 05:52:11 | 2016-04-24 20:52:11 |

+---------------------+---------------------+

 

root@test 05:52:53>insert into timezone_test values(current_timestamp,current_timestamp);

 

root@test 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 |

+---------------------+---------------------+

 

root@test 05:55:04>set time_zone='-7:00';

 

root@test 05:55:12>show variables like '%time_zone%';

+------------------+--------+

| Variable_name    | Value  |

+------------------+--------+

| system_time_zone | PDT    |

| time_zone        | SYSTEM |

+------------------+--------+

 

root@test 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

 

root@test 02:01:21>show variables like '%time_zone%';

+------------------+--------+

| Variable_name    | Value  |

+------------------+--------+

| system_time_zone | PDT    |

| time_zone        | SYSTEM |

+------------------+--------+

 

root@test 02:01:25>create table timestamp_test(t0 timestamp);

 

root@test 02:01:31>insert into timestamp_test values(current_timestamp);

 

root@test 02:01:46>select * from timestamp_test;

+---------------------+

| t0                  |

+---------------------+

| 2016-04-26 02:01:46 |

+---------------------+

 

新讀庫:

$date -R

Tue, 26 Apr 2016 16:52:39 +0800

 

devdba@test 05:02:54>show variables like '%time_zone%';

+------------------+--------+

| Variable_name    | Value  |

+------------------+--------+

| system_time_zone | CST    |

| time_zone        | SYSTEM |

+------------------+--------+

 

devdba@test 05:03:00>select * from timestamp_test;

+---------------------+

| t0                  |

+---------------------+

| 2016-04-26 17:01:46 |

+---------------------+

1 row in set (0.00 sec)

 

devdba@test 05:03:02>set time_zone='-7:00';

 

devdba@test 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端的時區信息為准。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM