内容目录
一、背景
在本地我们通常会用 docker compose 搭一套 MySQL,用于开发和测试;而生产环境往往是长期运行、经过多次配置调整的 MySQL 实例。
很多人以为:
只要 MySQL 版本一致(比如都是 8.0.32)就够了
但实际使用中会发现:
- 本地 SQL 能跑,线上却报错
- 本地写入成功,线上直接失败
- GROUP BY、日期、隐式转换行为不一致
其中一个最容易被忽略、但影响最大的原因就是:sql_mode 不一致。
二、sql_mode 是什么
sql_mode 决定了 MySQL 对 SQL 的语法校验、数据合法性检查和容错程度。
简单理解:
sql_mode 决定 MySQL 是“严格执行 SQL”,还是“尽量帮你兜底”
不同的 sql_mode 组合,会直接改变 SQL 的执行结果。
三、sql_mode 不一致可能导致的问题
1. 本地不报错,线上直接失败(严格模式)
常见相关配置:
STRICT_TRANS_TABLESNO_ZERO_DATENO_ZERO_IN_DATEERROR_FOR_DIVISION_BY_ZERO
可能问题:
- 插入超长字符串:本地被截断,线上报错
- 插入非法日期(如
2024-00-00):本地成功,线上失败 - 数值列插入非法值:本地变成 0,线上报错
👉 风险:开发阶段完全察觉不到,线上直接炸。
2. GROUP BY 行为不一致(ONLY_FULL_GROUP_BY)
如果开启:
ONLY_FULL_GROUP_BY
影响:
SELECT a, b FROM t GROUP BY a;
- 本地能跑(随机返回一个 b)
- 线上直接报错
👉 风险:老 SQL 或“宽松写法”在生产不可用。
3. 表结构和存储引擎不一致
如果没有:
NO_ENGINE_SUBSTITUTION
影响:
- 建表指定了不支持的存储引擎
- 本地或线上可能悄悄换成其他引擎(如 InnoDB)
👉 风险:事务、锁、性能行为出现差异。
4. 隐式转换和运算行为不同
如:
- 字符串转数字
- 除零
- CHAR 比较和填充
👉 风险:数据悄悄被“改写”,但你完全不知情。
四、如何确认生产使用的 sql_mode
在生产执行:
SELECT
@@GLOBAL.sql_mode AS global_sql_mode,
@@SESSION.sql_mode AS session_sql_mode;
⚠️ 注意:
- 应用真正使用的是 SESSION 级别
- 有些应用或连接池会在连接后
SET SESSION sql_mode=...
一定要确认:应用连接后的 sql_mode 是什么
五、本地 Docker MySQL 如何保持一致
1. 不要依赖默认值
不同镜像、不同初始化方式,MySQL 默认 sql_mode 可能不同。
结论:必须显式设置。
2. 推荐做法:通过 my.cnf 固定 sql_mode
在 docker compose 中挂载配置文件:
[mysqld]
sql_mode=STRICT_TRANS_TABLES,ONLY_FULL_GROUP_BY,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
⚠️ 务必使用生产环境的 sql_mode 原值
3. 启动后验证
SELECT @@GLOBAL.sql_mode, @@SESSION.sql_mode;
确保:
- 本地 GLOBAL 与生产一致
- 应用连接后的 SESSION 与生产一致
六、最佳实践总结
- ✅ MySQL 版本一致 不等于 行为一致
- ✅ 本地环境必须显式配置
sql_mode - ✅ 以生产应用实际使用的 SESSION sql_mode为准
- ✅ 把 sql_mode 配置纳入版本管理(my.cnf / compose)