一、sql_mode 概述
sql_mode 是 MySQL 的一个重要系统变量,它定义了 MySQL 应遵循的 SQL 语法和数据校验规则。通过设置不同的 sql_mode,可以控制 MySQL 的行为模式,包括:
- 数据校验的严格程度
- SQL 语法兼容性
- 查询行为
二、查看和设置 sql_mode
sql_mode是一个字符串变量,由多个"模式标志"(mode flags)组成,用逗号分隔
示例:
sql
SET sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO';
查看当前设置
sql
-- 查看当前会话的 sql_mode
SELECT @@sql_mode;
SELECT @@session.sql_mode;
-- 查看全局的 sql_mode
SELECT @@global.sql_mode;
-- 设置当前会话的 sql_mode
SET SESSION sql_mode = 'STRICT_TRANS_TABLES';
-- 设置全局 sql_mode(需要 SUPER 权限)
SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES';
-- 在配置文件中永久设置(my.cnf 或 my.ini)
[mysqld]
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE
三、主要 sql_mode 详解
1. STRICT_TRANS_TABLES
-
作用 :对事务性存储引擎(如 InnoDB)启用严格模式。
- 插入/更新时若数据非法(如超长、类型不匹配、超出范围),直接报错并拒绝操作,而不是警告+截断或转换。
- 对非事务表(如 MyISAM),只对"无法插入单行"的情况报错(不如
STRICT_ALL_TABLES严格)。
-
典型报错:
ERROR 1265 (01000): Data truncated for column 'name' at row 1 ERROR 1406 (22001): Data too long for column 'email' -
建议 :✅ 强烈推荐开启,防止脏数据写入。
2. STRICT_ALL_TABLES
sql
SET sql_mode = 'STRICT_ALL_TABLES';
CREATE TABLE test_myisam (id INT) ENGINE=MyISAM;
INSERT INTO test_myisam VALUES ('abc'); -- Error: 无效数值
- 作用 :对所有表(包括 MyISAM)启用严格模式。
- 与
STRICT_TRANS_TABLES区别:对非事务表也严格校验。 - 风险:MyISAM 表在批量插入时,若某一行失败,前面成功的行不会回滚(因为非事务),但会中断后续插入。
- 建议 :一般用
STRICT_TRANS_TABLES即可;除非你大量使用 MyISAM 且要求严格。
3. NO_ZERO_DATE
-
作用 :禁止
'0000-00-00'这种"零日期"。 -
典型报错:
sqlINSERT INTO t (dt) VALUES ('0000-00-00'); -- ERROR 1067 (42000): Invalid default value for 'dt' -- 或 ERROR 1292 (22007): Incorrect date value: '0000-00-00' -
注意 :即使列允许 NULL,显式插入
'0000-00-00'也会被拒。 -
建议:✅ 推荐开启,避免无意义日期。
4. NO_ZERO_IN_DATE
-
作用 :禁止年/月/日中出现"零",如
'2020-00-01'、'2020-01-00'(但'0000-00-00'由NO_ZERO_DATE控制)。 -
典型报错:
ERROR 1292 (22007): Incorrect date value: '2020-00-01' -
建议: 推荐开启。
5. ERROR_FOR_DIVISION_BY_ZERO
-
作用 :当 SQL 中出现除零操作(如
1 / 0)时,报错而非返回 NULL 或警告。 -
默认行为 (未开启时):返回
NULL并产生警告。 -
典型报错:
ERROR 1365 (22012): Division by 0 -
注意 :仅在
sql_mode包含此标志 且div_precision_increment等上下文触发时生效。 -
建议: 推荐开启,避免隐藏逻辑错误。
6. NO_AUTO_CREATE_USER(⚠️ 已废弃)
sql
-- MySQL 5.7 中
SET sql_mode = 'NO_AUTO_CREATE_USER';
GRANT SELECT ON db.* TO 'newuser'@'localhost'; -- Error
-- 必须先创建用户
CREATE USER 'newuser'@'localhost';
GRANT SELECT ON db.* TO 'newuser'@'localhost';
- 作用 (MySQL 5.7 及之前):禁止
GRANT语句自动创建用户。 - 现状 :从 MySQL 8.0 起已移除 ,因为 8.0 要求显式
CREATE USER。 - 无需关注(8.0+ 用户)。
7. NO_ENGINE_SUBSTITUTION
-
作用 :当建表指定的存储引擎不可用时(如指定
ENGINE=InnoDB但 InnoDB 被禁用),报错而不是静默替换为默认引擎(如 MyISAM)。 -
典型报错:
ERROR 1286 (42000): Unknown storage engine 'InnoDB' -
建议:✅ 强烈推荐开启,避免意外使用错误引擎。
8. ONLY_FULL_GROUP_BY
-
作用 :强制
GROUP BY查询中,SELECT列表只能包含分组列或聚合函数。 -
背景:MySQL 曾允许"非标准" GROUP BY(选择任意列),这在其他数据库(如 PostgreSQL)中是非法的。
-
典型报错:
sqlSELECT name, email FROM users GROUP BY dept_id; -- ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause... -
解决方案:
- 改写 SQL 使用
ANY_VALUE(name)(MySQL 特有) - 或确保 SELECT 列都在 GROUP BY 中
- 改写 SQL 使用
-
建议:✅ 推荐开启,保证 SQL 符合 ANSI 标准,避免歧义结果。
9. ANSI_QUOTES
-
作用 :将双引号
"视为标识符(如列名、表名)定界符,而不是字符串字面量。- 字符串必须用单引号
'。
- 字符串必须用单引号
-
影响:
sql-- 开启后,以下会报错(把 "hello" 当成列名): SELECT "hello"; -- ❌ Unknown column 'hello' -- 必须写成: SELECT 'hello'; -- ✅ -
典型报错:
ERROR 1054 (42S22): Unknown column 'xxx' in 'field list' -
建议 :除非你从 Oracle/PostgreSQL 迁移且习惯双引号,否则不要开启。
10. PIPES_AS_CONCAT
-
作用 :将
||视为字符串拼接操作符(类似 Oracle),而不是逻辑 OR。 -
默认行为 :
||是逻辑 OR(返回 0 或 1)。 -
示例:
sql-- 开启后: SELECT 'a' || 'b'; -- 返回 'ab' -- 关闭时: SELECT 'a' || 'b'; -- 返回 1(因为 'a' 和 'b' 都是非空,视为 true) -
建议 :一般不用,用
CONCAT()更清晰。
11. IGNORE_SPACE
sql
SET sql_mode = 'IGNORE_SPACE';
SELECT COUNT (*) FROM table; -- 有效
SET sql_mode = '';
SELECT COUNT (*) FROM table; -- 语法错误
- 作用 :允许函数名和括号之间有空格,如
COUNT (*)。 - 风险:可能导致函数名被误认为是保留字。
- 建议 :❌ 不推荐开启,保持
COUNT(*)标准写法。
12. TRADITIONAL
作用:组合严格模式,与传统数据库行为一致
sql
SET sql_mode = '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
四、sql_mode 组合模式
1. ANSI
sql
SET sql_mode = 'ANSI';
-- 等同于:
-- REAL_AS_FLOAT, PIPES_AS_CONCAT, ANSI_QUOTES,
-- IGNORE_SPACE, ONLY_FULL_GROUP_BY
2. TRADITIONAL(最严格)
sql
SET sql_mode = 'TRADITIONAL';
-- 所有严格检查的集合
五、常见错误及解决方案
错误1:GROUP BY 相关问题
ERROR 1055 (42000): Expression #1 is not in GROUP BY clause
解决方案:
sql
-- 方法1:修改 sql_mode
SET sql_mode = '';
-- 方法2:修改查询
SELECT region, MAX(product), SUM(amount)
FROM sales
GROUP BY region;
-- 方法3:使用 ANY_VALUE
SELECT region, ANY_VALUE(product), SUM(amount)
FROM sales
GROUP BY region;
错误2:日期格式问题
ERROR 1292 (22007): Incorrect datetime value
解决方案:
sql
-- 临时修改 sql_mode
SET sql_mode = '';
-- 或修正数据格式
INSERT INTO dates VALUES ('2023-01-01'); -- 正确格式
错误3:数据截断问题
ERROR 1406 (22001): Data too long for column
解决方案:
sql
-- 1. 修改字段长度
ALTER TABLE test MODIFY name VARCHAR(10);
-- 2. 截断数据
INSERT INTO test VALUES (SUBSTRING('abcd', 1, 3));
-- 3. 临时关闭严格模式
SET sql_mode = REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '');
六、最佳实践建议
开发环境
sql
-- 建议使用严格模式,及早发现问题
SET sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,ONLY_FULL_GROUP_BY,NO_ENGINE_SUBSTITUTION';
生产环境
sql
-- 根据应用需求调整
-- 迁移旧系统时可能需要宽松模式
SET sql_mode = ''; -- 兼容模式
-- 新系统建议严格模式
SET sql_mode = 'TRADITIONAL';
迁移注意事项
- 从旧版本升级时,检查 sql_mode 兼容性
- MySQL 8.0 默认启用 ONLY_FULL_GROUP_BY、STRICT_TRANS_TABLES
- 测试环境模拟生产环境的 sql_mode
七、版本差异
| MySQL 版本 | 默认 sql_mode |
|---|---|
| 5.6 | 空值 |
| 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 |
| 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
-- 查看当前设置的完整解释
SHOW VARIABLES LIKE 'sql_mode';
-- 动态修改(会话级别)
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_DATE';
-- 添加模式
SET SESSION sql_mode = CONCAT(@@sql_mode, ',ONLY_FULL_GROUP_BY');
-- 移除模式
SET SESSION sql_mode = REPLACE(@@sql_mode, 'ONLY_FULL_GROUP_BY', '');
-- 查看所有支持的 sql_mode
SELECT @@global.sql_mode, @@session.sql_mode\G