| 模式 | 包含關鍵標誌 | 核心特性 |
ANSI | REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE | ANSI SQL 兼容:|| 為字串連接、" 為識別符、函數名後不可加空格 |
TRADITIONAL | STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION | 嚴格模式聚合:非法日期報錯、除零報錯、嚴格事務/非事務表資料檢查 |
STRICT_TRANS_TABLES | 單一標誌 | 事務表:非法值報錯並回滾;非事務表:僅報警插入調整值 |
NO_ZERO_DATE / NO_ZERO_IN_DATE | 單一標誌 | 禁止 0000-00-00 / 禁止月日為 0 的日期(如 2024-00-01) |
NO_BACKSLASH_ESCAPES | 單一標誌 | 反斜線不作為轉義字元,視為普通字元 |
二、實測案例:同一資料在不同模式下的命運
場景 1:非法日期 '2007-04-31'(4 月無 31 日)
-- 預設模式(含 STRICT_TRANS_TABLES)
SET SESSION sql_mode = 'TRADITIONAL';
INSERT INTO t(d) VALUES('2007-04-31');
-- ERROR 1264 (22007): Out of range value for column 'd' at row 1
-- 寬鬆模式
SET SESSION sql_mode = 'ANSI';
INSERT INTO t(d) VALUES('2007-04-31');
-- Query OK, 1 row affected, 1 warning
SELECT * FROM t; -- 實際存入:0000-00-00 00:00:00
結論:TRADITIONAL 直接拒絕寫入並報錯;ANSI 僅警告並存入零日期,極易導致業務邏輯誤判。
場景 2:除零運算 9/0
SET SESSION sql_mode = 'TRADITIONAL';
INSERT INTO t(i) VALUES(9/0);
-- ERROR 1365 (22012): Division by 0
SET SESSION sql_mode = 'ANSI';
INSERT INTO t(i) VALUES(9/0);
-- Query OK, 1 row affected
SELECT * FROM t; -- 實際存入:NULL
結論:嚴格模式守護數值完整性;寬鬆模式靜默轉為 NULL,可能掩蓋業務 Bug。
場景 3:反斜線轉義 '\bw123' vs '\\bw123'
-- 預設模式(含 NO_BACKSLASH_ESCAPES 預設關閉)
SET SESSION sql_mode = 'ANSI';
INSERT INTO t(c) VALUES('\bw123'); -- \b 為退格符,實際存 'w123'
INSERT INTO t(c) VALUES('\\bw123'); -- 雙反斜線轉義為單反斜線,實際存 '\bw123'
-- 開啟 NO_BACKSLASH_ESCAPES
SET SESSION sql_mode = 'ANSI,NO_BACKSLASH_ESCAPES';
INSERT INTO t(c) VALUES('\bw123'); -- 字面存 '\bw123'
INSERT INTO t(c) VALUES('\\bw123'); -- 字面存 '\\bw123'
結論:轉義行為差異極大,**應用層必須與資料庫模式保持一致**,否則會出現資料顯示異常或 SQL 注入風險。
場景 4:識別符引用與 SHOW CREATE TABLE 輸出差異
-- ANSI 模式:雙引號為識別符
SET SESSION sql_mode = 'ANSI';
SHOW CREATE TABLE emp\G
-- 輸出:CREATE TABLE "emp" ("ename" varchar(10)...)
-- 加入 NO_TABLE_OPTIONS
SET SESSION sql_mode = 'ANSI,NO_TABLE_OPTIONS';
SHOW CREATE TABLE emp\G
-- 輸出:CREATE TABLE `emp` (`ename` varchar(10)...) -- 回歸反引號,移除 ENGINE/CHARSET 等 MySQL 專屬選項
結論:ANSI_QUOTES 導致雙引號無法作為字串界定符,**遷移異構資料庫時極易踩坑**;NO_TABLE_OPTIONS 可生成更通用的 DDL。
三、生產環境配置建議
推薦基準模式(MySQL 5.7/8.0 預設已包含大部分)
sql_mode = '
STRICT_TRANS_TABLES,
NO_ZERO_IN_DATE,
NO_ZERO_DATE,
ERROR_FOR_DIVISION_BY_ZERO,
NO_AUTO_CREATE_USER,
NO_ENGINE_SUBSTITUTION
'
解讀:
- 保留
STRICT_TRANS_TABLES 而非 STRICT_ALL_TABLES:非事務表(如 MyISAM 臨時表)不阻塞寫入
- 禁用
NO_AUTO_CREATE_USER:防止 GRANT 隱式建帳號(8.0 已移除)
- 禁用
NO_ENGINE_SUBSTITUTION:指定引擎不可用時拋錯而非靜默降級
避坑指南
- 全域 vs 會話:
SET GLOBAL 需 SUPER 權限且不影響現有連線;SET SESSION 即時生效僅限當前連線
- 持久化配置:必須寫入
my.cnf [mysqld] 區段,重啟生效
- 版本差異:5.7 預設含
ONLY_FULL_GROUP_BY,8.0 進一步強化;舊應用遷移需測試 GROUP BY 相容性
- 應用層協同:ORM 框架(Hibernate、MyBatis、GORM 等)連線參數需顯式設定
sessionVariables=sql_mode=... 確保一致
四、排查模式衝突的標準流程
-- 1. 檢視當前會話/全域模式
SELECT @@SESSION.sql_mode, @@GLOBAL.sql_mode;
-- 2. 解析模式組合
SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(@@sql_mode, ',', n), ',', -1)) AS flag
FROM (SELECT 1 n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) nums
WHERE n <= 1 + LENGTH(@@sql_mode) - LENGTH(REPLACE(@@sql_mode, ',', ''));
-- 3. 定位具體標誌影響
-- 參考官方文檔:https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html
sql_mode 不是「設定後就忘記」的參數,它是資料完整性的最後一道防線。建立團隊統一的模式基線、納入遷移測試清單、配置監控告警(如檢測到 sql_mode 為空或異常變更),才能真正發揮其守護資料質量的價值。
- 上一篇文章: MySQL 日誌體系完整指南:配置、分析與運維實戰
- 下一篇文章: MySQL 命令列核心操作全攻略:從安裝到表結構管理實戰
Copyright ©
程式碼之家 All Rights Reserved