ONLY_FULL_GROUP_BY和NO_ZERO_DATE无法阻止VARCHAR超长截断,因为它们仅分别约束SELECT分组逻辑和拦截'0000-00-00'日期,对DML写入行为完全无效;真正起作用的是STRICT_TRANS_TABLES或STRICT_ALL_TABLES。

为什么ONLY_FULL_GROUP_BY或NO_ZERO_DATE不能阻止VARCHAR超长截断
因为它们根本不管DML写入行为:ONLY_FULL_GROUP_BY只约束SELECT ... GROUP BY语句中非聚合字段的合法性;NO_ZERO_DATE只拦截'0000-00-00'这类日期字面量。向VARCHAR(5)插入'hello world'、向TINYINT插入300、向NOT NULL列插入NULL(无默认值)——这些操作在宽松模式下全部静默处理,不报错也不警告。
真正起作用的只有两个模式:
-
STRICT_TRANS_TABLES:对事务表(如InnoDB)启用严格校验,非事务表(如MyISAM)仍走宽松逻辑 -
STRICT_ALL_TABLES:对所有表强制严格,包括MyISAM,但可能让旧脚本批量失败
如何永久启用STRICT_TRANS_TABLES而不炸掉线上服务
必须改配置文件并重启MySQL,临时SET SESSION sql_mode只影响当前连接,毫无防护价值。Linux下编辑/etc/my.cnf,Windows下改my.ini,在[mysqld]段添加:
sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"
注意三点:
- 必须用双引号包裹整个值,漏引号会导致MySQL启动失败
- 不要加
ONLY_FULL_GROUP_BY进去凑数——它和DML非法写入无关,只影响查询 - 如果业务混用InnoDB和MyISAM,优先选
STRICT_TRANS_TABLES,避免MyISAM表因历史数据不规范而集体报错
开启严格模式后哪些ORM行为会突然崩掉
很多ORM(尤其是老版本Django、Laravel Eloquent、MyBatis)默认生成的INSERT语句不显式指定所有字段,依赖MySQL宽松模式自动填充默认值或空字符串。一旦启用STRICT_TRANS_TABLES,以下情况立即报错:
- INSERT INTO t (id) VALUES (1),但表有
NOT NULL的created_at字段且无默认值 → 报Field 'created_at' doesn't have a default value - INSERT批量写入含
NULL到NOT NULL字段(即使该字段有默认值)→ 严格模式下批量插入也拒绝NULL,不像宽松模式会转为空字符串 - UPDATE设置
status = ''到TINYINT字段 → 直接报Data truncated for column 'status'
上线前务必用真实SQL日志回放验证,别只靠单元测试。
为什么TRADITIONAL不是生产推荐选项
TRADITIONAL是复合模式,等价于STRICT_TRANS_TABLES + STRICT_ALL_TABLES + NO_AUTO_CREATE_USER + NO_AUTO_VALUE_ON_ZERO等一堆组合。问题在于它隐式启用了STRICT_ALL_TABLES,对MyISAM表也强制校验——而很多遗留系统仍有MyISAM日志表、统计表,字段长度没校验、默认值缺失,一开就全挂。
更可控的做法是显式列出你需要的模式:
- 必选:
STRICT_TRANS_TABLES - 建议搭配:
ERROR_FOR_DIVISION_BY_ZERO(防除零静默为NULL)、NO_ENGINE_SUBSTITUTION(防引擎被悄悄替换成MyISAM) - 谨慎加:
NO_ZERO_DATE——如果表里已有TIMESTAMP字段没设NULL也没给DEFAULT,MySQL 5.7+会自动填'0000-00-00 00:00:00',直接触发报错
最易被忽略的是:严格模式只对写入生效,SELECT完全不受影响;但一旦开了,所有INSERT/UPDATE/REPLACE都得过校验关,连LOAD DATA INFILE也不能幸免。


















