
peewee 在连接 mysql 时默认强制设置 `sql_mode="pipes_as_concat"`,会覆盖数据库全局 strict mode(如 `traditional`),导致 `null=false` 字段缺失时不报错——本文详解原因及三种可靠修复方案。
Peewee 的 MySQL 驱动(MySQLDatabase 及其子类 MySQLConnectorDatabase)为兼容性考虑,在初始化连接时主动注入 sql_mode="PIPES_AS_CONCAT"(见源码第 4147 行)。该行为会完全覆盖 MySQL 服务端配置的全局 sql_mode(例如通过 SET GLOBAL sql_mode = 'TRADITIONAL' 启用的严格模式),从而导致本应被拒绝的不完整 INSERT 操作(如省略 NOT NULL 字段)静默成功,插入默认值(如空字符串、0000-00-00 时间等),严重违背数据完整性预期。
这与 SQLite 行为形成鲜明对比:SQLite 下 Peewee 会主动校验 NOT NULL 约束并抛出 IntegrityError;而 MySQL 下因 SQL 模式被覆盖,校验权交还给了数据库驱动层,却未能触发严格约束。
✅ 解决方案一:显式指定完整 sql_mode(推荐)
在创建 MySQLDatabase 实例时,直接传入所需的严格模式组合(需包含 PIPES_AS_CONCAT 以保证 Peewee 功能正常):
debug_db = MySQLDatabase(
database='debug_db',
user='DEBUG',
host='localhost',
password='secret',
sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,PIPES_AS_CONCAT'
)? 提示:TRADITIONAL 是 MySQL 内置别名,等价于 STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL。若需精简,可选用 STRICT_TRANS_TABLES,PIPES_AS_CONCAT —— 它是兼顾安全与兼容的最小严格集。
✅ 解决方案二:复用全局模式 + 追加 PIPES_AS_CONCAT
若希望继承 MySQL 服务端全局 sql_mode 并仅追加 Peewee 所需特性,可禁用 Peewee 的 sql_mode 覆盖,并通过 init_command 动态拼接:
debug_db = MySQLDatabase(
database='debug_db',
user='DEBUG',
host='localhost',
password='secret',
sql_mode=None, # 关键:禁用 Peewee 默认覆盖
init_command="SET sql_mode = (SELECT CONCAT(@@sql_mode, ',PIPES_AS_CONCAT'));"
)⚠️ 注意:init_command 在连接建立后立即执行,依赖 @@sql_mode 变量,确保 MySQL 版本 ≥ 5.7.6(推荐 ≥ 8.0)。
✅ 解决方案三:使用 playhouse.pool.PooledMySQLDatabase(生产环境增强)
对于高并发场景,建议结合连接池与显式模式控制:
from playhouse.pool import PooledMySQLDatabase
debug_db = PooledMySQLDatabase(
database='debug_db',
user='DEBUG',
host='localhost',
password='secret',
max_connections=20,
stale_timeout=300,
sql_mode='STRICT_TRANS_TABLES,PIPES_AS_CONCAT'
)⚠️ 重要注意事项
- 不要依赖 null=False 的 Python 层校验:Peewee 不会在 .save() 前主动检查 None 值是否违反非空约束(除非字段显式赋 None,此时会触发 IntegrityError);
- default=None 是陷阱:这是 Peewee 所有字段的默认值,不代表“数据库默认值”,而是“无默认值”——必须由数据库或显式 default=/server_default= 提供;
- 验证是否生效:连接后执行 SELECT @@sql_mode; 确认实际生效的模式;
- 测试用例必备:在单元测试中模拟缺失必填字段场景(如 Person.create(first_name='Alice')),断言应抛出 peewee.IntegrityError。
通过以上任一方式正确配置 sql_mode,即可让 MySQL 严格模式真正生效,使 Peewee 的数据写入行为与数据库约束保持一致,从根本上保障数据质量与业务逻辑可靠性。










