最近測試組那邊反應數據庫部分寫入失敗,app層提示是插入成功,但表裏面裏面沒有產生數據,而兩個寫入操做的另一個表有數據。由於 insert 失敗在數據庫層面是看不出來的,因而找php的同事看下錯誤信息:php
1 |
[Err] 1364 - Field `f_company_id` doesn't have a default value |
很明顯2個 insert 操做,第一條成功,第二條失敗了,但由於沒有控制在一個事務當中,致使app裏面依然提示成功,這是客戶入庫操做,心想若是線上也有這個問題得是多大的代價。html
不說開發的問題,好端端的mysql怎麼忽然就部分表寫入失敗呢?根據上面的問題很快能猜到是 sql_mode 問題: NOT NULL 列沒有默認值但代碼裏也沒給值,在非嚴格模式下,int列默認爲0,string列默認爲’’了,因此不成問題;但在嚴格模式下,是直接返回失敗的。java
一看,果真:mysql
1 |
mysql> show variables like "sql_mode"; |
可是一直是沒問題的的,就忽然出現了,有誰會去改 sql_mode 呢,生產環境產生這個問題的風險有多大?因此必須揪出來。sql
先 set global sql_mode=''
,讓他們用着先(文後會給解決問題根本的辦法),同時打開general_log看是哪個用戶有相似設置 sql_mode 命令:數據庫
1 |
1134456 Query SET autocommit=1 |
看出是java組那邊哪一個框架創建鏈接的時候使用設置了sql_mode,但這是session級別的,不影響php那邊用戶的鏈接。session
那會是什麼緣由在 set global 以後又變回strict模式呢,因而想到 mysqld_safe 啓動實際是一個保護進程,在mysqld異常中止以後會拉起來,會不會中間有異常致使 mysqld 重啓,導致 global 失效?看了mysql錯誤日誌,纔想到前些天斷過電,因此決定直接改 /etc/my.cnf
配置:併發
1 |
[mysqld] |
重啓myqld以後,仍是STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
,不多遇到my.cnf裏面配置不生效的狀況。無獨有偶,在 stackoverflow上找到一樣的問題 how-to-make-sql-mode-no-engine-substitution-permanent-in-mysql-my-cnf ,緣由很簡單,sql_mode這個選項被其它地方的配置覆蓋了。oracle
瞭解一下mysql配置文件的加載順序:app
1 |
$ mysqld --help --verbose|grep -A1 -B1 cnf |
mysql按照上面的順序加載配置文件,後面的配置項會覆蓋前面的。最後終於在 /usr/my.cnf
找到有一條sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
,把這個文件刪掉,/etc/my.cnf 裏面的就生效了。
可是目前沒能整明白的是,mysql運行這麼長時間怎麼忽然在/usr
(MYSQL_BASE)下多個my.cnf,也不像人爲建立的。其它實例也沒這樣的問題。
相似還出現過一例:存儲過程裏把 ‘’ 傳給int型的,嚴格模式是不容許,而非嚴格模式只是一個warning。(命令行執行完語句後,show warnings
可看見)
那麼解決這類問題的終極(推薦)辦法實際上是,考慮到數據的兼容性和準確性,MySQL就應該運行在嚴格模式下!不管開發環境仍是生產環境,不然代碼移植到線上可能產生隱藏的問題。
sql_mode 問題能夠很簡單,也能夠很複雜。曾經在一個交流羣裏看到有人提到,主從sql_mode設置不一致致使複製異常,這裏本身正好全面瞭解一下幾個經常使用的值,方便之後排除問題多個方向。
官方手冊專門有一節介紹 https://dev.mysql.com/doc/refman/5.6/en/sql-mode.html 。 SQL Mode 定義了兩個方面:MySQL應支持的SQL語法,以及應該在數據上執行何種確認檢查。
SQL語法支持類
ONLY_FULL_GROUP_BY
對於GROUP BY聚合操做,若是在SELECT中的列、HAVING或者ORDER BY子句的列,沒有在GROUP BY中出現,那麼這個SQL是不合法的。是能夠理解的,由於不在 group by 的列查出來展現會有矛盾。
在5.7中默認啓用,因此在實施5.6升級到5.7的過程須要注意:
1 |
Expression #1 of SELECT list is not in GROUP BY |
ANSI_QUOTES
啓用 ANSI_QUOTES 後,不能用雙引號來引用字符串,由於它被解釋爲識別符,做用與 ` 同樣。
設置它之後,update t set f1="" ...
,會報 Unknown column ‘’ in ‘field list 這樣的語法錯誤。
PIPES_AS_CONCAT
||
視爲字符串的鏈接操做符而非 或 運算符,這和Oracle數據庫是同樣的,也和字符串的拼接函數 CONCAT() 相相似NO_TABLE_OPTIONS
SHOW CREATE TABLE
時不會輸出MySQL特有的語法部分,如 ENGINE
,這個在使用 mysqldump 跨DB種類遷移的時候須要考慮。NO_AUTO_CREATE_USER
GRANT ... ON ... TO dbuser
順道一塊兒建立用戶。設置該選項後就與oracle操做相似,受權以前必須先創建用戶。5.7.7開始也默認了。數據檢查類
NO_ZERO_DATE
NO_ZERO_DATE
,效果與上面同樣,’0000-00-00’容許但顯示warning;若是沒有設置NO_ZERO_DATE
,no warning,當作徹底合法的值。NO_ZERO_IN_DATE
狀況與上面相似,不一樣的是控制日期和天,是否可爲 0 ,即 2010-01-00
是否合法。NO_ENGINE_SUBSTITUTION
ALTER TABLE
或CREATE TABLE
指定 ENGINE 時, 須要的存儲引擎被禁用或未編譯,該如何處理。啓用NO_ENGINE_SUBSTITUTION
時,那麼直接拋出錯誤;不設置此值時,CREATE用默認的存儲引擎替代,ATLER不進行更改,並拋出一個 warning .STRICT_TRANS_TABLES
STRICT_TRANS_TABLES
不是幾種策略的組合,單獨指 INSERT
、UPDATE
出現少值或無效值該如何處理:上面並無囊括全部的 SQL Mode,選了幾個表明性的,詳細仍是 看手冊。
sql_mode通常來講不多去關注它,沒有遇到實際問題以前不會去啓停上面的條目。咱們常設置的 sql_mode 是 ANSI
、STRICT_TRANS_TABLES
、TRADITIONAL
,ansi和traditional是上面的幾種組合。
ANSI
:更改語法和行爲,使其更符合標準SQLTRADITIONAL
:更像傳統SQL數據庫系統,該模式的簡單描述是當在列中插入不正確的值時「給出錯誤而不是警告」。ORACLE
:至關於 PIPES_AS_CONCAT, ANSI_QUOTES, IGNORE_SPACE, NO_KEY_OPTIONS, NO_TABLE_OPTIONS, NO_FIELD_OPTIONS, NO_AUTO_CREATE_USER不管何種mode,產生error以後就意味着單條sql執行失敗,對於支持事務的表,則致使當前事務回滾;但若是沒有放在事務中執行,或者不支持事務的存儲引擎表,則可能致使數據不一致。MySQL認爲,相比直接報錯終止,數據不一致問題更嚴重。因而 STRICT_TRANS_TABLES
對非事務表依然儘量的讓寫入繼續,好比給個」最合理」的默認值或截斷。而對於 STRICT_ALL_TABLES
,若是是單條更新,則不影響,但若是更新的是多條,第一條成功,後面失敗則會出現部分更新。
5.6.6 之後版本默認就是NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
,5.5默認爲 ‘’ 。
查看
1 |
查看當前鏈接會話的sql模式: |
設置
1 |
形式如 |
配置文件裏面設置sql-mode=""
。
updated: 2017-12-10
現網作數據遷移測試時報另外一個錯誤,起因是這樣的:一個1.8億的表裏面,由於某種緣由須要把字段定義null改成not null,避免下游服務(如ES)處理特殊數據時異常狀況。但這是一個併發dml很是高又達到100多G的大表,online ddl針對這種修改字段類型簡直一籌莫展,pt-osc也慢的很。
恰好有一個作數據遷移的契機,本來打算在新庫上把字段改好,再經過dts或相似的數據遷移工具,同步過去。在非嚴格模式下,本來是null的值也會變成0或’’,但仍是報錯了:
1 |
set sql_mode=''; -- 置爲非嚴格模式 |
找到官方文檔上的原話,能夠解釋:
If you are not using strict mode, then whenever you insert an 「incorrect」 value into a column, such as a NULL into a NOT NULL column or a too-large numeric value into a numeric column, MySQL sets the column to the 「best possible value」 instead of producing an error
If you try to store NULL into a column that doesn’t take NULL values, an error occurs for single-row INSERT statements. For multiple-row INSERT statements or for INSERT INTO … SELECT statements, MySQL Server stores the implicit default value for the column data type
非嚴格模式下,單行插入 null 到 not null 列,會失敗;多行插入則只是warning。規則是這樣,也就無需解釋。