1. 程式人生 > 其它 >MySQL中“詭異”的TIMESTAMP資料型別

MySQL中“詭異”的TIMESTAMP資料型別

技術標籤:mysqljava資料庫pythonsql

前 言

涉及MySQL的日常開發與運維過程中少不了和TIMESTAMP資料型別打交道,有時候TIMESTAMP型別看起來莫名其妙,測試環境都測試OK了上了預發就直接報錯了;有時候TIMESTAMP型別看起來又很詭異,表中的真實資料跟開發人員提交的表結構又南轅北轍並非所需要的;本篇文章將抽絲剝繭帶你重新認識一下這個熟悉又陌生的TIMESTAMP型別。

1、TIMESTAMP影響因素之一——MySQL時區簡介

1.1 GMT、UTC、CST簡介

GMT:格林尼治標準時間(Greenwich Mean Time),簡稱GMT。是指位於英國倫敦郊區的皇家格林尼治天文臺的標準時間,格林尼治的經線被定義為本初子午線,作為計算地理的起點和世界標準"時區"的起點,即:零時區的時間。

UTC:協調世界時(Universal Time Coordinated),簡稱UTC,又稱世界標準時間。取代GMT的新計量標準。

CST:北京時間(China Standard Time),簡稱CST,又稱中國標準時間,即:東八區的時間。由於在時區劃分上,比協調世界時早了8個小時,固記為UTC+8

1.2 GMT和UTC的關係

GMT是之前的時間計量標準,是根據地球自轉和公轉來計算時間,也就是太陽每天經過格林尼治天文臺的時間就是中午12點,誤差較大。

UTC是現用的時間計量標準,是根據原子鐘來計算時間,誤差較小。

現在世界上最精確的原子鐘50億年會產生誤差1秒,可以說非常精確。而GMT因為是根據地球的轉動來計算時間的,而地球的自轉正在緩速變慢,所以使用GMT的話,總會有一天(可能很久很久),中午12點,並不是一天太陽當頭照的時候,很可能就是早上或者晚上了。所以說UTC更加精確。當前看來兩者是約等於關係。

1.3 CST和GMT、UTC之間的關係

根據不同時區可以看到幾者之間的關係是:

CST=UTC+8小時

CST=GMT+8小時

UTC≈GMT

1.4 舉個栗子,說說清楚:

時區的存在是因為地球上不同地方的人看到太陽升起的時間是不一樣的。假定北京人民在早上8:00看到了太陽剛剛升起,而此刻歐洲人民(假設東1區)還在夜裡,他們還需要再過7個小時才能看到太陽升起,所以此刻歐洲人民的手錶上顯示的是凌晨1:00,如果你強迫他們用北京時間那他們每天看到日出的時間就是15點。

即:東8區的北京人民的手錶顯示的8:00和東1區歐洲人民手錶顯示的1:00是相同的時刻:

"2017-06-29 08:00:00 +8:00" = "2017-06-29 01:00:00 +1:00"

2、TIMESTAMP影響因素之二——SQL_MODE簡介

SQL Mode定義了MySQL支援的SQL語法以及應該在資料上執行何種驗證檢查,常見的SQL_MODE歸類如下:

2.1 SQL語法支援類

ANSI:等同於REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE

IGNORE_SPACE:允許函式名和'(' 之間有空格

REAL_AS_FLOAT:將REAL視為FLOAT的同義詞,而不是DOUBLE的同義詞

PIPES_AS_CONCAT:將 '||' 視為字串的連線操作符而非 '或' 運算子

ONLY_FULL_GROUP_BY:查詢欄位必須出現在聚合操作的欄位中

NO_AUTO_CREATE_USER:不自動建立使用者,即授權時候必須先建立使用者之後才可以授權

NO_TABLE_OPTIONS:SHOW CREATE TABLE 時不會輸出MySQL特有的語法部分,如ENGINE資訊

ANSI_QUOTES:雙引號是識別符作用同反引號:SHOW CREATE TABLE時可以看到,UPDATE tab SET col1="a",此時的"a"會當做一個欄位名,而不是某個值

2.2 驗證檢查類

NO_ZERO_DATE:認為日期 '0000-00-00' 非法,跟是否設定後面的嚴格模式有關。

NO_ZERO_DATE且在嚴格模式下NO_ZERO_DATE自然滿足,INSERT和UPDATE全0不再被允許;INSERT IGNORE和UPDATE IGNORE全0被允許但是報warning

NO_ZERO_DATE且在非嚴格模式下INSERT和UPDATE和INSERT IGNORE和UPDATE IGNORE全0被允許但是報warning

NO_ZERO_IN_DATE:同NO_ZERO_DATE類似,在年份非0的前提下,月和日是否允許為0,即2010-01-00或者2010-00-01是否合法

NO_ENGINE_SUBSTITUTION:在ALTER TABLE或CREATE TABLE時候,指定的ENGINE如果被禁用或未編譯直接拋錯

STRICT_TRANS_TABLES:嚴格模式,嚴格檢查在INSERT、UPDATE等操作出現少值或無效值該如何處理

線上常用的SQL_MODE有ANSI、STRICT_TRANS_TABLES,推薦使用嚴格模式

3、TIMESTAMP型別和DATETIME型別的比較

TypeStorage(Bytes)Minimum ValueMaximum Value是否跟時區相關是否有預設行為
int4-21474836482147483647
timestamp41970-01-01 00:00:01(UTC)2038-01-19 03:14:07(UTC)
datetime81000-01-01 00:00:009999-12-31 23:59:59

注意:從5.6.4版本開始,TIME,TIMESTAMP,DATTIME這三種類型增加了對小數秒的支援,timestamp儲存大小為4Bytes+小數部分;datetime儲存大小為5Bytes+小數部分。

詳見:

Date and Time Data Type Representation(https://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html)

#驗證了TIMESTAMP型別採用INT儲存,具體差8小時原因,請檢視1:時區部分
mysql> SELECT FROM_UNIXTIME(0);
+---------------------+
| FROM_UNIXTIME(0)    |
+---------------------+
| 1970-01-01 08:00:00 |
+---------------------+
1 row in set (0.00 sec)

mysql> SELECT FROM_UNIXTIME(2147483647);
+---------------------------+
| FROM_UNIXTIME(2147483647) |
+---------------------------+
| 2038-01-19 11:14:07       |
+---------------------------+
1 row in set (0.00 sec)

相同點:

a、兩者都可以表示時間精確到秒且顯示格式都為:YYYY-MM-DD HH:MM:SS

不同點:

a、TIMESTAMP長度4位元組而DATETIME長度8位元組,比較節省儲存空間

b、TIMESTAMP型別採用INT儲存,排序效率更高,查詢速度更快,方便計算

c、TIMESTAMP有效範圍是1970-2038,而DATETIME有效範圍是1000-9999

d、TIMESTAMP型別有預設行為,而DATETIME則沒有

e、TIMESTAMP型別會受時區的影響,而DATETIME則不會(見案例六)

f、TIMESTAMP型別會受SQL_MODE的影響,而DATETIME則不會

4、TIMESTAMP型別的預設行為

4.1 預設行為規則如下:

a、如果TIMESTAMP列沒有明確宣告NULL屬性,則預設是NOT NULL(如果是其他的資料型別,如果沒有明確宣告NULL屬性則預設為NULL)

b、表中的第一個TIMESTAMP欄位,如果沒有明確宣告NULL、DEFAULT、ON UPDATE會自動分配DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP屬性(見案例一)

c、表中的第二個及以後TIMESTAMP欄位,如果沒有明確宣告NULL、DEFAULT會預設分配'0000-00-00 00:00:00'屬性(見案例二)

4.2 使用規則如下:

a、在INSERT或者UPDATE語句中設定了TIMESTAMP欄位為NULL時,若該欄位允許為NULL,則結果為NULL;若該欄位不允許為NULL,則結果為當前的時間戳,跟DEFAULT沒有關係(見案例四)

b、在INSERT時若預設(即INSERT語句不指定該列的值),則結果為預設值,具體值由DEFAULT決定(見案例五)

c、若有一個欄位屬性是ON UPDATE CURRENT_TIMESTAMP,則修改該行的任何欄位都會更新此TIMESTAMP欄位為當前時間戳。

4.3 預設行為的特殊限制

a、5.1和5.5版本每張表只能有一個DEFAULT CURRENT_TIMESTAMP型別的TIMESTAMP欄位,如果多了會報錯:there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause

b、5.6和5.7則沒有這個限制:每張表可以由多個DEFAULT CURRENT_TIMESTAMP型別的TIMESTAMP欄位

c、引數explicit_defaults_for_timestamp(釋義:顯示指定TIMESTAMP型別)可以控制TIMESTAMP型別的預設行為,預設是OFF,即使用預設行為,不手動顯示指定

4.4 案例篇

預設行為規則+使用規則+特殊限制放在一起暈了沒?我們少廢話,上案例

案例一:驗證了4.1-b的預設行為

mysql> #執行SQL
mysql> CREATE TABLE tab(
    -> id BIGINT unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
    -> db_create_time TIMESTAMP
    -> )ENGINE=INNODB DEFAULT CHARSET=UTF8;
Query OK, 0 rows affected (0.01 sec)

mysql> #SHOW CREATE TABLE檢視
mysql> #5.5版本 & 5.6版本 & 5.7版本
mysql> SHOW CREATE TABLE tab\G
*************************** 1. row ***************************
       Table: tab
Create Table: CREATE TABLE `tab` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `db_create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

案例二:驗證了4.1-bc的預設行為

mysql> #執行SQL
mysql> CREATE TABLE tab1(
    -> id BIGINT unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
    -> db_create_time TIMESTAMP,
    -> db_update_time TIMESTAMP
    -> )ENGINE=INNODB DEFAULT CHARSET=UTF8;
Query OK, 0 rows affected (0.01 sec)

mysql> #SHOW CREATE TABLE檢視
mysql> #5.5版本 & 5.6版本 & 5.7版本
mysql> SHOW CREATE TABLE tab1 \G
*************************** 1. row ***************************
       Table: tab1
Create Table: CREATE TABLE `tab1` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `db_create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `db_update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

案例三:驗證了4.3-a的特殊限制

mysql> #執行SQL
mysql> CREATE TABLE tab2(
    -> id BIGINT unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
 -> db_create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 -> db_update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    -> )ENGINE=INNODB DEFAULT CHARSET=UTF8;
Query OK, 0 rows affected (0.01 sec)

mysql> #SHOW CREATE TABLE檢視
mysql> #5.6版本 & 5.7版本
mysql> SHOW CREATE TABLE tab2 \G
*************************** 1. row ***************************
       Table: tab2
Create Table: CREATE TABLE `tab2` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 `db_create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 `db_update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
      
      
#5.5版本報錯了(MySQL5.5版本不支援多個DEFAULT CURRENT_TIMESTAMP屬性的欄位)

      
ERROR 1293 (HY000): Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause
      
      

     
     

案例四:驗證了4.2-a的使用規則

#5.5版本 & 5.6版本 & 5.7版本
mysql> #執行SQL
mysql> CREATE TABLE tab3(
    -> id BIGINT unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
    -> db_create_time TIMESTAMP  NOT NULL DEFAULT '2000-01-01 00:00:00'
    -> )ENGINE=INNODB DEFAULT CHARSET=UTF8;
Query OK, 0 rows affected (0.00 sec)


mysql> SHOW CREATE TABLE tab3 \G
*************************** 1. row ***************************
       Table: tab3
Create Table: CREATE TABLE `tab3` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `db_create_time` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)

mysql> INSERT INTO tab3 VALUES(1,NULL);
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM tab3;
+----+---------------------+
| id | db_create_time      |
+----+---------------------+
|  1 | 2017-06-29 22:46:53 |
+----+---------------------+
1 row in set (0.01 sec)