解释 CHECK 表达式返回 UNKNOWN 时的约束判断并给出显式非空写法
来源:17golang原创
时间:2026-10-13 05:52:54 265浏览 收藏
我第一次遇到这个问题,是在给金额字段补 CHECK 约束时:表达式明明写成了 amount > 0,但插入 NULL 并没有像插入负数一样失败。答案不在于 CHECK 失效,而在于 SQL 的三值逻辑:比较 NULL 得到的是 UNKNOWN,而 MySQL 的 CHECK 约束允许表达式结果为 TRUE 或 UNKNOWN,只有 FALSE 才会触发约束违规。
如果业务要求“金额不能缺失,也必须大于 0”,要把两个意图分别写出来:用 NOT NULL 拒绝缺失值,再用 CHECK (amount > 0) 拒绝非正数。本文只围绕这一条边界展开,不把它扩展成 SQL_MODE 的全面配置说明。
官方地址:https://dev.mysql.com/doc/refman/8.4/en/create-table-check-constraints.html
我先把 CHECK 的三个结果分开看
排查时,我会先把“表达式算出了什么”和“约束是否接受这一行”分成两件事。以 CHECK (amount > 0) 为例,常见输入可以整理成下面这样:
| amount 输入 | 比较结果 | CHECK 是否拒绝 | 原因 |
|---|---|---|---|
10 | TRUE | 不拒绝 | 满足正数条件 |
0 或负数 | FALSE | 拒绝 | 明确违反条件 |
NULL | UNKNOWN | 不由 CHECK 拒绝 | NULL 不是一个可直接比较的数值 |
这里最容易混淆的是“UNKNOWN 不是 TRUE”。它只是表示当前比较无法得到确定的真或假;但在 MySQL CHECK 的约束判定里,允许通过的集合包含 UNKNOWN。因此,CHECK (amount > 0) 不是“非空且为正数”的完整表达。

为什么 NULL 会绕过看似严格的比较
我通常把 NULL 理解成“值缺失或未知”,而不是 0、空字符串或某个特殊数字。于是 NULL > 0、NULL = 0 都不能得到普通的 TRUE/FALSE。只要表达式中的一部分是 NULL,比较结果就可能变成 UNKNOWN。
这也解释了两个看起来矛盾的现象:
- 负数会触发
CHECK (amount > 0),因为结果是 FALSE。 - NULL 不会被这条 CHECK 单独拦住,因为结果是 UNKNOWN,而 MySQL 把 UNKNOWN 视为可接受的约束结果。
所以不要把“CHECK 没有报错”直接等同于“所有业务条件都满足”。它只说明这条表达式没有得到 FALSE。是否允许缺失,是列定义和业务规则共同决定的事情。
需要非空时,把两个约束职责写在一起
如果金额必须存在且为正数,我会采用下面这种显式组合。SQL 注释只标出每条约束负责的边界,便于以后修改表结构时不把职责混在一起。
CREATE TABLE payment (
-- NOT NULL 单独负责拒绝缺失值
id BIGINT PRIMARY KEY,
amount DECIMAL(12, 2) NOT NULL,
-- CHECK 单独负责拒绝 0 和负数
CONSTRAINT payment_amount_positive CHECK (amount > 0)
);
这段定义的含义是:NOT NULL 负责拒绝缺失值,CHECK 负责拒绝 0 和负数。它们不是重复配置,而是两个互补边界。把 NOT NULL 省略后,CHECK 仍然可能允许 NULL;把 CHECK 省略后,非空金额又可能是 0 或负数。
如果要把字段声明写得更接近列本身,也可以把 CHECK 放在列定义中:
CREATE TABLE stock_item (
-- 主键保证每条库存记录有稳定标识
id BIGINT PRIMARY KEY,
-- 非空与非负条件共同约束库存数量
quantity INT NOT NULL CHECK (quantity >= 0)
);
列级 CHECK 适合只依赖当前列的条件;表级 CHECK 更适合比较多个列,例如最低价不高于最高价。无论放在哪里,都不能用 CHECK 的 UNKNOWN 行为代替明确的非空声明。

可空字段与显式非空字段,应该怎样选
这里没有“一律加 NOT NULL”这个架构答案。我会先问字段的缺失状态有没有业务含义:
| 业务含义 | 推荐写法 | 要接受的结果 |
|---|---|---|
| 尚未采集,之后允许补齐 | amount DECIMAL(12,2) CHECK (amount > 0) | NULL 代表未采集,正数代表有效值 |
| 创建记录时必须给出 | amount DECIMAL(12,2) NOT NULL CHECK (amount > 0) | NULL 和非正数都被拒绝 |
| 缺失与 0 是两个不同状态 | 保留 NULL,同时保留 CHECK | 不能用默认值 0 偷换缺失语义 |
我的经验是先确定“NULL 是否有意义”,再决定是否加 NOT NULL。若 NULL 代表“未知”,让它通过 CHECK 可能正是正确设计;若 NULL 代表脏数据或漏传,则必须在列定义中显式禁止。
迁移和排查时,我会检查这几项
- 先看实际表结构:使用
SHOW CREATE TABLE your_table,确认 CHECK 是否存在、是否为ENFORCED,以及列是否带NOT NULL。 - 再看表达式边界:把 NULL、0、负数和一个合法正数分别代入思考,不要只拿一个失败样例判断规则。
- 确认约束职责:需要拒绝 NULL 时,检查的是列定义;需要拒绝具体取值时,检查的是 CHECK 表达式。
- 检查写入路径:INSERT、UPDATE、REPLACE、LOAD DATA 等会触发 CHECK 评估;不要只在应用层做校验后就假设数据库规则完整。
-- 查看表定义,确认 NOT NULL 与 CHECK 是否都存在
SHOW CREATE TABLE payment;
-- 只检查可能的缺失数据,不把 NULL 当成 0
SELECT id, amount
FROM payment
WHERE amount IS NULL OR amount
排查时尤其不要写成 amount = NULL。判断缺失值要使用 IS NULL 或 IS NOT NULL;等号比较仍然遵循 NULL 的未知语义。
最后记住这条组合规则
MySQL CHECK 的判断重点不是“表达式是否为真”,而是“表达式是否明确为假”:TRUE 通过,UNKNOWN 也通过,FALSE 才违规。因而 CHECK (amount > 0) 只约束确定的非正数,并不会自动表达“字段必须存在”。
需要“存在且合法”时,使用 NOT NULL + CHECK;需要保留“尚未知道”的状态时,允许 NULL,但在查询和应用逻辑中用 IS NULL 明确处理。把缺失语义和数值范围拆成两个约束,后续迁移、排错和团队协作都会更清楚。
相关问题
CHECK (amount > 0) 能替代 NOT NULL 吗?
不能。NULL 参与比较后可能得到 UNKNOWN,而 MySQL CHECK 允许 UNKNOWN。要拒绝 NULL,需要在列定义上写 NOT NULL。
把 CHECK 改成 CHECK (amount IS NOT NULL AND amount > 0) 可以吗?
可以表达非空且为正数,但我更建议保留 NOT NULL 与 CHECK 的分工,让列的可空性在字段定义中一眼可见。
UNKNOWN 是数据库中的实际存储值吗?
不是。UNKNOWN 是条件表达式的逻辑结果,通常由 NULL 参与运算产生;它不等同于某个可以存入数值列的第三种数据值。
-
374 收藏
-
499 收藏
-
384 收藏
-
184 收藏
-
265 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习