必须启用 STRICT_TRANS_TABLES 或 STRICT_ALL_TABLES 才能阻止 DML 非法数据静默截断或修正;ONLY_FULL_GROUP_BY 和 NO_ZERO_DATE 仅分别约束 SELECT 分组和特定日期格式,对 NOT NULL 插入 NULL、VARCHAR 超长截断、TINYINT 溢出等 DML 问题完全无效。

必须启用 STRICT_TRANS_TABLES(或 STRICT_ALL_TABLES),否则任何其他模式都无法阻止 DML 中的非法数据静默截断或修正。
为什么只设 ONLY_FULL_GROUP_BY 或 NO_ZERO_DATE 没用?
这些模式各自只管一类行为:ONLY_FULL_GROUP_BY 仅影响 SELECT 的分组逻辑,NO_ZERO_DATE 只拦截 '0000-00-00' 这种日期,但对以下典型 DML 非法写入完全不设防:
- 向
NOT NULL字段插入NULL(宽松模式下静默转为默认值或空字符串) - 向
VARCHAR(5)插入'hello world'(被无声截断为'hello') - 向
TINYINT插入300(被强制转成127或报警告)
只有 STRICT_TRANS_TABLES(事务表)或 STRICT_ALL_TABLES(所有表)才会让这些操作直接报错中断。
STRICT_TRANS_TABLES 和 STRICT_ALL_TABLES 怎么选?
优先用 STRICT_TRANS_TABLES,它只对 InnoDB 等事务表生效,对 MyISAM 表仍保持原有宽松行为——这在混合引擎环境中更可控,也避免因旧 MyISAM 表结构不规范导致批量失败。
-
STRICT_ALL_TABLES更激进:MyISAM 写入超长字符串也会报错,但可能暴露历史遗留问题(如未加长度校验的导入脚本) - 两者都不影响读操作,只作用于
INSERT、UPDATE、REPLACE等写入语句 - 若业务全用 InnoDB,二者效果一致;若有 MyISAM 表且无法改造,
STRICT_TRANS_TABLES是更稳妥的选择
临时设置 vs 永久配置的关键区别
临时设置只对当前连接有效,重启客户端就失效,**不能用于生产防护**:
- 会话级:执行
SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO'; - 全局级(不推荐):执行
SET GLOBAL sql_mode = '...';后,已存在的连接仍沿用旧模式,新连接才生效 - 永久配置必须改
my.cnf(Linux)或my.ini(Windows)的[mysqld]段,并重启 MySQL 服务 - 配置项写法必须带双引号:
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO",漏引号会导致启动失败
容易被忽略的兼容性陷阱
启用严格模式后,以下行为会立即报错,但常被 ORM 或旧脚本依赖:
- Django/SQLAlchemy 自动生成的
INSERT ... VALUES (DEFAULT, ...)在字段含NOT NULL但无默认值时失败 - PHP
mysqli批量插入含空字符串到INT字段(如' '或'')触发类型转换错误 - MySQL 5.7+ 默认已启用部分严格模式,但升级后若配置文件中覆盖了
sql_mode却漏写STRICT_TRANS_TABLES,等于主动降级 -
ERROR_FOR_DIVISION_BY_ZERO必须和严格模式共存才真正生效——单独开启只让除零产生警告,搭配STRICT_*才报错
上线前务必在测试环境跑通全部 DML 脚本和 ORM 数据操作,别等生产 INSERT 失败才查日志。


















