MySQL 8.0+ 默认严格模式为 STRICT_TRANS_TABLES,仅对新连接生效且受全局/会话两级配置影响;改sql_mode未生效多因仅修改会话级、连接池复用旧连接或配置文件优先级更高需重启。

MySQL 8.0+ 默认 strict mode 是什么,为什么你改了 sql_mode 还没生效
MySQL 8.0 开始默认启用严格模式(STRICT_TRANS_TABLES),但这个“默认”只作用于新连接、且受全局与会话两级配置影响。你执行 SET sql_mode = 'STRICT_TRANS_TABLES' 后插入脏数据仍成功,大概率是改了会话级却没动全局级,或者应用连接池复用了旧连接。
-
SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES'才影响后续新连接;已有连接仍用旧值 - MySQL 配置文件(
my.cnf或my.ini)里的sql_mode设置优先级高于启动后动态 SET,但需重启生效 - Docker 容器或云数据库(如阿里云 RDS)可能锁定全局
sql_mode,只能通过会话 SET,且每次连接都要重设
如何安全地启用完整严格校验:STRICT_TRANS_TABLES vs STRICT_ALL_TABLES
两者都开启“拒绝非法值”,但行为关键不同:STRICT_TRANS_TABLES 只对事务表(InnoDB)强制校验,STRICT_ALL_TABLES 对所有表(包括 MyISAM)都生效——但 MyISAM 不支持事务回滚,出错时已写入的部分无法撤回,反而导致数据不一致。
- 生产环境只用
STRICT_TRANS_TABLES,别加STRICT_ALL_TABLES - 真正需要强校验时,补上
NO_ZERO_DATE、NO_ZERO_IN_DATE、ERROR_FOR_DIVISION_BY_ZERO,防止日期/除零静默转成默认值 - 避免包含
ALLOW_INVALID_DATES—— 它会让'2023-02-30'这种错日期被接受
推荐组合:STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO
PHP/Laravel 连接 MySQL 时 sql_mode 被悄悄覆盖怎么办
很多 ORM 或 PDO 驱动会在连接建立后自动执行 SET sql_mode = '' 或覆盖为你指定的值,尤其 Laravel 的 mysql 配置里 'strict' => true 实际发的是 SET sql_mode = 'STRICT_TRANS_TABLES',但若 DBA 在服务器端锁死配置,这条语句会被忽略且无报错。
- 检查 PHP 连接后的实际生效值:
SELECT @@sql_mode,别只信配置文件 - Laravel 中在
config/database.php的mysql配置下加'options' => [PDO::MYSQL_ATTR_INIT_COMMAND => "SET sql_mode=(SELECT REPLACE(@@sql_mode,'STRICT_TRANS_TABLES',''))"]这类写法很危险,会主动关掉严格模式 - 更稳妥的做法:在 MySQL 侧固化配置(
my.cnf+ 重启),让所有客户端统一遵守,而非各框架各自 SET
INSERT IGNORE / ON DUPLICATE KEY UPDATE 在 strict mode 下的行为变化
非 strict 模式下,INSERT IGNORE 遇到主键冲突或数据截断会静默跳过;strict 模式下,**数据截断会直接报错(Truncated incorrect DOUBLE value 类错误),而主键冲突仍被 IGNORE**——这容易让人误以为“strict 没起作用”。
- 想让重复键也报错?别用
IGNORE,改用INSERT ... ON DUPLICATE KEY UPDATE并确保UPDATE子句里没有非法值 - 批量插入时,strict mode 下一行失败会导致整批回滚(事务表),而非只跳过那行
- 测试时用
SHOW WARNINGS查看是否出现Data truncated提示,它在 strict 下会升级为 ERROR
真正麻烦的不是设不设 strict,而是团队里有人写 SQL 依赖隐式转换(比如把字符串 'abc' 插进 INT 字段),这种代码在 strict 下必挂,但问题根源不在参数,而在数据契约本身没定义清楚。

















