checkconstraint是django 4.2+数据库级强制校验,check参数必须为q对象(如q(age__gte=18)),禁止字符串、lambda或跨模型引用;字段名用双下划线语法,null需显式处理;约束名须全局唯一,抽象基类中应使用'%(app_label)s_%(class)s'模板;它不替代full_clean()等应用层校验,仅作为数据完整性兜底。

CheckConstraint 是 Django 4.2 中真正能落地的数据完整性防线——它不是装饰,而是数据库原生的强制校验,绕过 ORM、杜绝竞态,但用错就等于没用。
CheckConstraint 的 check 表达式必须用 Q 对象,不能写 Python 逻辑
常见错误是把 check 写成函数调用或布尔表达式,比如 check=age >= 18 或 check=lambda: ...。Django 会直接报 NotSupportedError,因为这类表达式无法翻译成 SQL CHECK 子句。
-
check参数只能接受Q对象(来自django.db.models),例如Q(age__gte=18) - 字段名必须用双下划线语法(
status__in、published_at__isnull),不支持__contains、__regex等依赖数据库函数的查找 - 禁止跨模型引用字段(如
author__is_active),只允许本模型字段 - 示例:限制草稿状态不能有发布时间 ——
Q(status="draft") & Q(published_at__isnull=True) | Q(status="published")
NULL 值必须显式处理,否则 PostgreSQL/SQLite 会静默拒绝插入
数据库对 CHECK 中涉及 NULL 的表达式返回 UNKNOWN,而 PostgreSQL 和 SQLite 将其视为“违反约束”,导致 INSERT 失败;MySQL 则可能放行(行为不一致)。所以不能假设 Q(age__gte=0) 自动兼容空值。
- 正确写法:必须显式覆盖 NULL 场景,例如
Q(age__gte=0) | Q(age__isnull=True) - 错误写法:
Q(age__gte=0)—— 当age为NULL时,整个条件求值为UNKNOWN,被数据库判定为不满足 - 特别注意
BooleanField:Q(is_active=True)不等价于Q(is_active__isnull=False) & Q(is_active=True),后者才安全
约束名(name)必须全局唯一,抽象基类中要用模板变量生成
数据库层面约束名是 schema 级别唯一的。如果多个模型继承同一抽象基类并定义同名 CheckConstraint,迁移时会撞名,报 duplicate constraint name 或 constraint already exists。
- 绝对不要在抽象基类中硬编码
name="my_check" - 必须使用占位符:
name="%(app_label)s_%(class)s_age_gte_18",Django 会在迁移时自动替换为blog_article_age_gte_18这类具体名 - 重命名约束后,旧约束不会自动删除——需手动写迁移文件调用
RemoveConstraint,否则残留约束可能干扰新逻辑
CheckConstraint 不替代应用层校验,二者职责不同
有人以为加了 CheckConstraint 就不用写 clean() 或表单 validators,这是危险误区。数据库约束只拦截 INSERT/UPDATE,但不参与 Django 的模型验证流程(如 full_clean())、不提供用户友好的错误提示、也不触发 admin 或 DRF 的序列化错误。
-
CheckConstraint是兜底:防 raw SQL、bulk_create、事务并发写入导致的数据污染 - 应用层校验仍是必需:提供即时反馈、支持复杂逻辑(如跨字段比较、调用外部 API)、适配前端交互
- 两者共存时,建议给
CheckConstraint配violation_error_message,让validate()报错时消息更明确,例如violation_error_message="年龄不能小于 18 岁"
pg_constraint 或 sqlite_master),而不是只信迁移输出。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











