讓天下沒有難用的資料庫 » Incorrect datetime value
今天在開發庫上給一個表新增欄位時候,發現居然報錯:
[email protected] 06:14:42>ALTER TABLE `DB`.` user` ADD COLUMN `status_mode` TINYINT UNSIGNED AFTER ` test_id`;
ERROR 1292 (22007): Incorrect datetime value: ‘0000-00-00 00:00:00’ for column ‘GMT_CLEANUP’ at row 2;
查詢error的資訊:
$perror 1292
錯誤:1292 SQLSTATE: 22007 (ER_TRUNCATED_WRONG_VALUE)
訊息:截短了不正確的%s值: ‘%s’
這種解釋有點讓人不明白。
接著想到mysql中alter table add column執行時會對原表進行臨時複製,在副本上進行更改,然後刪除原表,再對新表進行重新命名。那麼報錯的原因就是在ddl過程中copy原表,在copy表的過程中發現有表中GMT_CLEANUP的資料為’0000-00-00 00:00:00’,mysql認為該資料是不合法的資料:
[email protected]:39:56>select GMT_CLEANUP from user where GMT_CLEANUP like ‘%00%’ limit 2
-> ;
+———————+
| GMT_CLEANUP |
+———————+
| 0000-00-00 00:00:00 |
| 0000-00-00 00:00:00 |
+———————+
2 rows in set, 1 warning (0.00 sec)
第一個問題:那麼為什麼mysql認為0000-00-00 00:00:00是不正確的?
第二個問題0000-00-00 00:00:00是怎麼被插入到資料庫中的,應用有這個需求嗎?
對於第一個問題,還是需要回到mysql中對日期時間的定義上,在官方文件上說明MySQL允許將’0000-00-00’儲存為“偽日期”(如果不使用NO_ZERO_DATE SQL模式)。這在某些情況下比使用NULL值更方便(並且資料和索引佔用的空間更小)。那麼接下來,就是看看sql_mode中的引數了;
[email protected]:40:48>show variables like ‘sql_mode’;
+—————+——————————————————————————————————————————-+
| Variable_name | Value |
+—————+——————————————————————————————————————————-+
| sql_mode | STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER |
+—————+——————————————————————————————————————————-+
1 row in set (0.00 sec)
引數中含有no_zero_date,
在嚴格模式,不要將 ‘0000-00-00’做為合法日期。你仍然可以用IGNORE選項插入零日期。在非嚴格模式,可以接受該日期,但會生成警告。
NO_ZERO_IN_DATE
在嚴格模式,不接受月或日部分為0的日期。如果使用IGNORE選項,我們日期插入’0000-00-00’。在非嚴格模式,可以接受該日期,但會生成警告。
問題可以解決了,改變sql_mode:將no_zero_date和no_zero_in_date去掉:
[email protected] 07:05:21>set global sql_mode=’STRICT_TRANS_TABLES,STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER’;
Query OK, 0 rows affected (0.00 sec)
[email protected] 07:07:01>show variables like ‘%sql_mode%’;
+—————+——————————————————————————————————————————-+
| Variable_name | Value |
+—————+——————————————————————————————————————————-+
| sql_mode | STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER |
+—————+——————————————————————————————————————————-+
1 row in set (0.00 sec)
雖然去掉了no_zero_date和no_zero_in_date,但在引數中還有這兩個引數的存在,於是exit該會話,在檢視引數的值依然無效:
[email protected] 07:07:10>exit
Bye
$mysql -uroot DB
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 23505133
Server version: 5.1.37-log Source distribution
Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.
[email protected] 07:07:41>show variables like ‘%sql_mode%’;
+—————+——————————————————————————————————————————-+
| Variable_name | Value |
+—————+——————————————————————————————————————————-+
| sql_mode | STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER |
+—————+——————————————————————————————————————————-+
1 row in set (0.00 sec)
為什麼會出現這種情況,是不是引數設定的不對,查看了其他庫中sql_mode的引數,都沒有設定,唯獨這個庫中sql_mode設定了,看來需要對sql_mode做詳細的瞭解了:
簡單說sql_mode是設定mysql應該支援哪些sql語法,以及哪種資料驗證檢查。這樣可以更容易地在不同的環境中使用MySQL,並結合其它資料庫伺服器使用MySQL。可以通過用SET [SESSION|GLOBAL] sql_mode=’modes’語句設定sql_mode變數來更改SQL模式。設定 GLOBAL變數時需要擁有SUPER許可權,並且會影響從那時起連線的所有客戶端的操作。設定SESSION變數隻影響當前的客戶端。任何客戶端可以隨時更改自己的會話 sql_mode值。
當前資料庫的sql_mode中有:
STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER
這些值,這是將sql_mode設為了:TRADITIONAL模式,所以只去除NO_ZERO_IN_DATE,NO_ZERO_DATE是不行的,還要去除TRADITIONAL;
[email protected] 07:07:43>set global sql_mode=’STRICT_TRANS_TABLES,STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER’;
Query OK, 0 rows affected (0.00 sec)
退出來後,重新登入:
[email protected] 07:20:47>show variables like ‘%sql_mode%’;
+—————+————————————————————————————–+
| Variable_name | Value |
+—————+————————————————————————————–+
| sql_mode | STRICT_TRANS_TABLES,STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER |
+—————+————————————————————————————–+
1 row in set (0.00 sec)
[email protected] 07:20:48>ALTER TABLE `DB`.`ali_mall_user` ADD COLUMN `status_mode` TINYINT UNSIGNED AFTER `op_invest_id`;
Query OK, 106 rows affected (0.66 sec)
Records: 106 Duplicates: 0 Warnings: 0
已經看到可以修改表的結構了,現在mysql允許ddl了。
對於第二個問題:為什麼會插入0000-00-00 00:00:00?
。嚴格模式允許日期使用“零”部分,例如’2004-04-00’或“零”日期。要想禁止,應在嚴格模式基礎上,啟用NO_ZERO_IN_DATE和NO_ZERO_DATE SQL模式。
。每個時間型別有一個有效值範圍和一個“零”值,當指定不合法的MySQL不能表示的值時使用“零”值。
。無效DATETIME、DATE或者TIMESTAMP值被轉換為相應型別的“零”值(‘0000-00-00 00:00:00’、’0000-00-00’或者00000000000000)。
從上面的sql_mode中可以看到在嚴格模式(啟用STRICT_TRANS_TABLES或STRICT_ALL_TABLES模式)是可以插入:0000-00-00 00:00:00’,但是後面還啟動了TRADITIONAL,TRADITIONAL中還有NO_ZERO_IN_DATE和NO_ZERO_DATE 模式,所以前期插入了0000-00-00 00:00:00資料,後面有改動了sql_mode,最後導致前面插入插入的資料變為了不合法,所以才會出現上面總總問題。