1. 程式人生 > >讓天下沒有難用的資料庫 » Incorrect datetime value

讓天下沒有難用的資料庫 » 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

[[email protected] ~]

$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,最後導致前面插入插入的資料變為了不合法,所以才會出現上面總總問題。