SQL_MODE是MySQL中的一個系統變數(variable),可由多個MODE組成,每個MODE控制一種行為,如是否允許除數為0,日期中是否允許'0000-00-00'值。 為什麼需要關註SQL_MODE呢? 首先,看三個簡單的Demo(MySQL 5.6)。 1. 實際存儲值與插入值不符。 2 ...
SQL_MODE是MySQL中的一個系統變數(variable),可由多個MODE組成,每個MODE控制一種行為,如是否允許除數為0,日期中是否允許'0000-00-00'值。
為什麼需要關註SQL_MODE呢?
首先,看三個簡單的Demo(MySQL 5.6)。
1.
mysql> create table t1(c1 datetime); Query OK, 0 rows affected (0.16 sec) mysql> insert into t1 values('2019-02-29'); Query OK, 1 row affected, 1 warning (0.01 sec) mysql> select * from t1; +---------------------+ | c1 | +---------------------+ | 0000-00-00 00:00:00 | +---------------------+ 1 row in set (0.00 sec)
實際存儲值與插入值不符。
2.
mysql> create table t2(c1 varchar(10)); Query OK, 0 rows affected (0.06 sec) mysql> insert into t2 values('a'),('b'),('c'); Query OK, 3 rows affected (0.01 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> select * from t2; +------+ | c1 | +------+ | a | | b | | c | +------+ 3 rows in set (0.00 sec) mysql> alter table t2 modify column c1 int; Query OK, 3 rows affected, 3 warnings (0.05 sec) Records: 3 Duplicates: 0 Warnings: 3 mysql> show warnings; +---------+------+-------------------------------------------------------+ | Level | Code | Message | +---------+------+-------------------------------------------------------+ | Warning | 1366 | Incorrect integer value: 'a' for column 'c1' at row 1 | | Warning | 1366 | Incorrect integer value: 'b' for column 'c1' at row 2 | | Warning | 1366 | Incorrect integer value: 'c' for column 'c1' at row 3 | +---------+------+-------------------------------------------------------+ 3 rows in set (0.00 sec) mysql> select * from t2; +------+ | c1 | +------+ | 0 | | 0 | | 0 | +------+ 3 rows in set (0.00 sec)
DDL導致原列內容丟失。
3.
mysql> create table t3(id int not null,c1 varchar(10)); Query OK, 0 rows affected (0.05 sec) mysql> insert into t3 values(null,'a'); ERROR 1048 (23000): Column 'id' cannot be null mysql> insert into t3(c1) values('a'); Query OK, 1 row affected, 1 warning (0.00 sec) mysql> show warnings; +---------+------+-----------------------------------------+ | Level | Code | Message | +---------+------+-----------------------------------------+ | Warning | 1364 | Field 'id' doesn't have a default value | +---------+------+-----------------------------------------+ 1 row in set (0.00 sec) mysql> select * from t3; +----+------+ | id | c1 | +----+------+ | 0 | a | +----+------+ 1 row in set (0.00 sec)
顯式指定列和不顯式指定的處理邏輯竟然不一樣。
為什麼會這樣呢?這個即與SQL_MODE有關。
在MySQL 5.6中, SQL_MODE的預設值為"NO_ENGINE_SUBSTITUTION",非嚴格模式。
在這種模式下,在進行數據變更操作時,如果涉及的列中存在無效值(如日期不存在,數據類型不對,數據溢出),只會提示"Warning",並不會報錯。
如果要規避上述問題,需開啟SQL_MODE的嚴格模式。
SQL_MODE的嚴格模式
所謂的嚴格模式,即SQL_MODE中開啟了STRICT_ALL_TABLES或STRICT_TRANS_TAB LES。
還是上面的Demo,看看嚴格模式下,MySQL的處理邏輯。
mysql> set session sql_mode='STRICT_TRANS_TABLES'; Query OK, 0 rows affected (0.00 sec) mysql> insert into t1 values('2019-02-29'); ERROR 1292 (22007): Incorrect datetime value: '2019-02-29' for column 'c1' at row 1 mysql> alter table t2 modify column c1 int; ERROR 1366 (HY000): Incorrect integer value: 'a' for column 'c1' at row 1 mysql> insert into t3(c1) values('a'); ERROR 1364 (HY000): Field 'id' doesn't have a default value
同樣的SQL,在嚴格模式下,直接提示"ERROR",而不是"Warning"。
同是嚴格模式,下麵看看STRICT_ALL_TABLES或STRICT_TRAN S_TABLES的區別。
STRICT_ALL_TABLES與STRICT_TRANS_TABLES的區別
STRICT_TRANS_TABLES只對事務表開啟嚴格模式,STRICT_ALL_TABLES是對所有表開啟嚴格模式,不僅僅是事務表,還包括非事務表。
看下麵這個測試。
對myisam表插入3條數據,其中,第3條數據是空字元串,與定義的int類型不匹配。
mysql> create table t (c1 int) engine=myisam; Query OK, 0 rows affected (0.00 sec) mysql> set session sql_mode='STRICT_TRANS_TABLES'; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> insert into t values (1),(2),(''); Query OK, 3 rows affected, 1 warning (0.00 sec) Records: 3 Duplicates: 0 Warnings: 1 mysql> show warnings; +---------+------+------------------------------------------------------+ | Level | Code | Message | +---------+------+------------------------------------------------------+ | Warning | 1366 | Incorrect integer value: '' for column 'c1' at row 3 | +---------+------+------------------------------------------------------+ 1 row in set (0.00 sec) mysql> select * from t; +------+ | c1 | +------+ | 1 | | 2 | | 0 | +------+ 3 rows in set (0.00 sec) mysql> set session sql_mode='STRICT_ALL_TABLES'; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> insert into t values (1),(2),(''); ERROR 1366 (HY000): Incorrect integer value: '' for column 'c1' at row 3
可以看到,在表為myisam存儲引擎的情況下,只有開啟STRICT_ALL_TABLES才會報錯。
不同版本預設的SQL_MODE
MySQL 5.5:空
MySQL 5.6:NO_ENGINE_SUBSTITUTION
MySQL 5.7:ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION
MySQL 8.0:ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, NO_ENGINE_SUBSTITUTION
如何修改SQL_MODE
SQL_MODE既可在全局級別修改,又可在會話級別修改。可指定多個MODE,MODE之間用逗號隔開。
全局級別
set global sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES';
會話級別
set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES';
SQL_MODE的完整列表
ALLOW_INVALID_DATES
在嚴格模式下,對於日期的檢測較為嚴格,其必須有效。若開啟該MODE,對於month和day的檢測會相對寬鬆。其中,month只需在1~12之間,day只需在1~31之間,而不管其是否有效,如下麵的'2004-02-31'。
mysql> create table t (c1 datetime); Query OK, 0 rows affected (0.21 sec) mysql> set session sql_mode='STRICT_TRANS_TABLES'; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> insert into t values('2004-02-31'); ERROR 1292 (22007): Incorrect datetime value: '2004-02-31' for column 'c1' at row 1 mysql> set session sql_mode='STRICT_TRANS_TABLES,ALLOW_INVALID_DATES'; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> insert into t values('2004-02-31'); Query OK, 1 row affected (0.01 sec) mysql> select * from t; +---------------------+ | c1 | +---------------------+ | 2004-02-31 00:00:00 | +---------------------+ 1 row in set (0.00 sec)
註意,該MODE只適用於DATE和DATETIME,不適用於TIMESTAMP。
ANSI_QUOTES
在MySQL中,對於關鍵字和保留字,是不允許用做表名和欄位名的。如果一定要使用,必須使用反引號("`")進行轉義。
mysql> create table order (id int); ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order (id int)' at line 1 mysql> create table `order` (id int); Query OK, 0 rows affected (0.12 sec)
若開啟該MODE,則雙引號,同反引號一樣,可對關鍵字和保留字轉義。
mysql> set session sql_mode=''; Query OK, 0 rows affected (0.00 sec) mysql> create table "order" (c1 int); ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"order" (c1 int)' at line 1 mysql> set session sql_mode='ANSI_QUOTES'; Query OK, 0 rows affected (0.00 sec) mysql> create table "order" (c1 int); Query OK, 0 rows affected (0.17 sec)
需要註意的是,在開啟該MODE的情況下,不能再用雙引號來引字元串。
ERROR_FOR_DIVISION_BY_ZERO
該MODE決定除數為0的處理邏輯,實際效果還取決於是否開啟嚴格模式。
1. 開啟嚴格模式,且開啟該MODE,插入1/0,會直接報錯。
mysql> create table t (c1 double); Query OK, 0 rows affected (0.04 sec) mysql> set session sql_mode='STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO'; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> insert into t values(1/0); ERROR 1365 (22012): Division by 0
2. 只開啟嚴格模式,不開啟該MODE,允許1/0的插入,且不提示warning,1/0最後會轉化為NULL。
mysql> set session sql_mode='STRICT_TRANS_TABLES'; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> insert into t values(1/0); Query OK, 1 row affected (0.07 sec) mysql> select * from t; +------+ | c1 | +------+ | NULL | +------+ 1 row in set (0.00 sec)
3. 不開啟嚴格模式,只開啟該MODE,允許1/0的插入,但提示warning。
4. 不開啟嚴格模式,也不開啟該MODE,允許1/0的插入,且不提示warning,同2一樣。
HIGH_NOT_PRECEDENCE
預設情況下,NOT的優先順序低於比較運算符。但在某些低版本中,NOT的優先順序高於比較運算符。
看看兩者的區別。
mysql> set session sql_mode=''; Query OK, 0 rows affected (0.00 sec) mysql> select not 1 < -1; +------------+ | not 1 < -1 | +------------+ | 1 | +------------+ 1 row in set (0.00 sec) mysql> set session sql_mode='HIGH_NOT_PRECEDENCE'; Query OK, 0 rows affected (0.00 sec) mysql> select not 1 < -1; +------------+ | not 1 < -1 | +------------+ | 0 | +------------+ 1 row in set (0.00 sec)
在sql_mode為空的情況下, not 1 < -1相當於not (1 < -1),如果設置了'HIGH_ NOT_PRECEDENCE',則相當於(not 1) < -1。
IGNORE_SPACE
預設情況下,函數名和左括弧(“(”)之間不允許存在空格。若開啟該MODE,則允許。
mysql> set session sql_mode=''; Query OK, 0 rows affected (0.00 sec) mysql> select count(*) from t; +----------+ | count(*) | +----------+ | 2 | +----------+ 1 row in set (0.00 sec) mysql> select count (*) from t; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '*) from t' at line 1 mysql> set session sql_mode='IGNORE_SPACE'; Query OK, 0 rows affected (0.01 sec) mysql> select count (*) from t; +-----------+ | count (*) | +-----------+ | 2 | +-----------+ 1 row in set (0.01 sec)
NO_AUTO_VALUE_ON_ZERO
預設情況下,在對自增主鍵插入NULL或0時,會自動生成下一個值。若開啟該MODE,當插入0時,並不會自動生成下一個值。
如果表中自增主鍵列存在0值,在進行邏輯備份還原時,可能會導致數據不一致。所以mysqldump在生成備份數據之前,會自動開啟該MODE,以避免數據不一致的情況。
mysql> create table t (id int auto_increment primary key); Query OK, 0 rows affected (0.11 sec) mysql> set session sql_mode=''; Query OK, 0 rows affected (0.01 sec) mysql> insert into t values (0); Query OK, 1 row affected (0.04 sec) mysql> select * from t; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec) mysql> set session sql_mode='NO_AUTO_VALUE_ON_ZERO'; Query OK, 0 rows affected (0.02 sec) mysql> insert into t values (0); Query OK, 1 row affected (0.09 sec) mysql> select * from t; +----+ | id | +----+ | 0 | | 1 | +----+ 2 rows in set (0.00 sec)
NO_BACKSLASH_ESCAPES
預設情況下,反斜杠“\”會作為轉義符,若開啟該MODE,則反斜杠“\”會作為一個普通字元,而不是轉義符。
mysql> set session sql_mode=''; Query OK, 0 rows affected (0.01 sec) mysql> select '\\t'; +----+ | \t | +----+ | \t | +----+ 1 row in set (0.00 sec) mysql> set session sql_mode='NO_BACKSLASH_ESCAPES'; Query OK, 0 rows affected (0.00 sec) mysql> select '\\t'; +-----+ | \\t | +-----+ | \\t | +-----+ 1 row in set (0.00 sec)
NO_DIR_IN_CREATE
預設情況下,在創建表時,可以指定數據目錄(DATA DIRECTORY)和索引目錄(INDEX DIRECTORY),若開啟該MODE,則會忽略這兩個選項。在主從複製場景下,可在從庫上開啟該MODE。
mysql> set session sql_mode=''; Query OK, 0 rows affected (0.01 sec) mysql> create table t (id int) data directory '/tmp/'; Query OK, 0 rows affected (0.15 sec) mysql> show create table t\G *************************** 1. row *************************** Table: t Create Table: CREATE TABLE `t` ( `id` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci DATA DIRECTORY='/tmp/' 1 row in set (0.00 sec) mysql> set session sql_mode='NO_DIR_IN_CREATE'; Query OK, 0 rows affected (0.00 sec) mysql> drop table t; Query OK, 0 rows affected (0.11 sec) mysql> create table t (id int) data directory '/tmp/'; Query OK, 0 rows affected, 1 warning (0.05 sec) mysql> show create table t\G *************************** 1. row *************************** Table: t Create Table: CREATE TABLE `t` ( `id` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci 1 row in set (0.00 sec)
NO_ENGINE_SUBSTITUTION
在開啟該MODE的情況下,在創建表時,如果指定的存儲引擎不存在或不支持,則會直接提示“ERROR”。
若不開啟,則只會提示“Warning”,且使用預設的存儲引擎。
mysql> set session sql_mode=''; Query OK, 0 rows affected (0.00 sec) mysql> create table t (id int) engine=federated; Query OK, 0 rows affected, 2 warnings (0.11 sec) mysql> show warnings; +---------+------+-------------------------------------------+ | Level | Code | Message | +---------+------+-------------------------------------------+ | Warning | 1286 | Unknown storage engine 'federated' | | Warning | 1266 | Using storage engine InnoDB for table 't' | +---------+------+-------------------------------------------+ 2 rows in set (0.00 sec) mysql> show create table t\G *************************** 1. row *************************** Table: t Create Table: CREATE TABLE `t` ( `id` int(11) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci 1 row in set (0.01 sec) mysql> drop table t; Query OK, 0 rows affected (0.11 sec) mysql> set session sql_mode='NO_ENGINE_SUBSTITUTION'; Query OK, 0 rows affected (0.00 sec) mysql> create table t (id int) engine=federated; ERROR 1286 (42000): Unknown storage engine 'federated'
NO_UNSIGNED_SUBTRACTION
兩個整數相減,如果其中一個數是無符號位,預設情況下,會產生一個無符號位的值,如果該值為負數,則會提示“ERROR”。如,
mysql> set session sql_mode=''; Query OK, 0 rows affected (0.00 sec) mysql> select cast(0 as unsigned)-1; ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(cast(0 as unsigned) - 1)'
若開啟該MODE,則允許結果為負數。
mysql> set session sql_mode='NO_UNSIGNED_SUBTRACTION'; Query OK, 0 rows affected (0.00 sec) mysql> select cast(0 as unsigned)-1; +-----------------------+ | cast(0 as unsigned)-1 | +-----------------------+ | -1 | +-----------------------+ 1 row in set (0.00 sec)
NO_ZERO_DATE
該MODE會影響'0000-00-00'的插入。實際效果還取決於是否開啟嚴格模式。
1. 在開啟嚴格模式,且同時開啟該MODE,是不允許'0000-00-00'插入的。
mysql> set session sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE'; Query OK, 0 rows affected, 1 warning (0.00 sec) mysql> show warnings\G *************************** 1. row ***************************