MySQL時間類型和模式

當我在MySQL數據庫中嘗試插入一條帶有時間戳的數據時報錯:

mysql> insert into alarm_service values (6, '1970-01-01 08:00:00'); 
ERROR 1292 (22007): Incorrect datetime value: '1970-01-01 08:00:00' for column 'time' at row 1

# 查看錶結構
mysql> show create table alarm_service;
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table         | Create Table                                                                                                                                                                                                                         |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| alarm_service | CREATE TABLE `alarm_service` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 |
+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

我們可以發現錯誤信息提示是時間值錯誤,但是我們這明顯是一個合法的時間點啊。

經過查詢資料,發現原因是在MySQL中,timestamp類型的合法區間是1970-01-01 00:00:01 - 2038-01-19 03:14:07 UTC,而在存儲是,會先將你插入的數據轉換爲UTC時間,然後存儲起來,讀取的時候,再轉換爲你的本地時間。由於我的時區爲東八區,因此轉換後就變爲了1970-01-01 00:00:00 UTC,成爲了非法時間。

解決方案爲:

  1. 調整時間爲合法範圍
  2. 調整MySQL嚴格模式,允許非法時間

下面我們詳細說明相關的內容。

MySQL時間類型

MySQL時間類型分爲三種:

  • DATE:用於只包含日期不包含時間的時候,MySQL會將格式轉換爲YYYY-MM-DD,合法範圍爲1000-01-01 - 9999-12-31
  • DATETIME:用於包含日期+時間的時候,格式爲YYYY-MM-DD HH:MM:SS,合法範圍爲1000-01-01 00:00:00 - 9999-12-31 23:59:59
  • TIMESTAMP:用於包含日期+時間的時候,格式爲YYYY-MM-DD HH:MM:SS,合法範圍爲1997-01-01 00:00:01 - 2038-01-19 03:14:07 UTC

同時,DATETIMETIMESTAMP還都支持一個6位微秒的數據支持,格式爲YYYY-MM-DD HH:MM:SS[.fraction],合法範圍爲.000000 - .999999

DATETIMETIMESTAMP還都提供自動初始化並更新爲當前日期和時間的數據。

對於TIMESTAMP類型,MySQL會在存儲時將數據值轉換爲UTC標準時間來存儲,讀取時再轉爲當前時間。如果你的時區沒有發生改變,則該值就是你存儲的值,如果你改變了時區,讀取到的值就會發生變化。這個特性不會對DATETIME生效。

查看時區

mysql> show variables like '%zone%';                                       
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | CST    |
| time_zone        | SYSTEM |
+------------------+--------+

可以看到當前設置的時區是SYSTEM,即跟操作系統保持一致,同時系統的時區是CST(China Standard Time 北京標準時間),查看系統時間也可以看到是東8區(+0800)

$ date -R
Tue, 23 Apr 2019 11:22:47 +0800

因此我們輸入1970-01-01 08:00:00時MySQL會糾正爲1970-01-01 00:00:00,而成爲一個非法值。

非法時間值

對於非法的時間值,針對不同的時間類型,MySQL會將其轉爲合適的值:0000-00-00 或 0000-00-00 00:00:00

比如月份爲1-12月,當你嘗試插入2019-13-01 00:00:00時,就會被糾正爲0000-00-00 00:00:00,因爲不存在13月,爲非法值。

嚴格模式

當我們插入非法時間值時,雖然會被糾正,但是在嚴格模式下,不會插入數據,反而會報錯:

ERROR 1292 (22007): Incorrect datetime value: '1970-01-01 08:00:00' for column 'time' at row 1

我們可以通過設置模式,來調整MySQL的行爲,首先查看MySQL的模式:

mysql> show variables like '%sql_mode%';            
+----------------------------+--------------------------------------------+
| Variable_name              | Value                                      |
+----------------------------+--------------------------------------------+                               |
| sql_mode                   | STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION |
+----------------------------+--------------------------------------------+

在這個模式下,非法時間會直接報錯,我們可以調整模式爲ALLOW_INVALID_DATES

mysql> set session sql_mode = 'ALLOW_INVALID_DATES';
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like '%sql_mode%';            
+---------------+---------------------+
| Variable_name | Value               |
+---------------+---------------------+
| sql_mode      | ALLOW_INVALID_DATES |
+---------------+---------------------+
1 row in set (0.00 sec)

在這個模式下,不會再完備檢查日期的合法性,只會檢查月份的範圍在1-12,日期在1-31。這在處理用戶輸入的時候很合適,但是這個模式只對於DATEDATETIME很合適,對於TIMESTAMP,依然需要一個合法的值,否則就會糾正爲0000-00-00 00:00:00

在非法值時,如果這個模式啓用,就會報錯;如果禁用,就會糾正爲0000-00-00 00:00:00併產生一個警告:

mysql> insert into alarm_service values (7, '1970-01-01 08:00:00'); 
Query OK, 1 row affected, 1 warning (0.00 sec)

總結

對於這種問題,有兩種解決方法:

  1. 調整時間爲合法範圍
  2. 調整MySQL嚴格模式,允許非法時間

參考資料

  1. 11.3.1 The DATE, DATETIME, and TIMESTAMP Types:https://dev.mysql.com/doc/ref...
  2. 5.1.13 MySQL Server Time Zone Support: https://dev.mysql.com/doc/ref...
  3. 5.1.11 Server SQL Modes: https://dev.mysql.com/doc/ref...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章