强烈建议关联字段加 NOT NULL 约束——因 NULL 参与 ON 条件时恒返回 UNKNOWN,导致 INNER JOIN 丢数据、LEFT JOIN 右表字段全为 NULL;外键不自动禁止 NULL,须显式声明 NOT NULL 并预先清理存量 NULL。

不是必须,但强烈建议——因为 NULL 会直接让 ON 条件失效,而你通常意识不到它在“静默丢数据”。
INNER JOIN 的 ON 条件遇到 NULL 就自动跳过
SQL 的三值逻辑决定:NULL = 123、NULL = NULL、123 = NULL 全部返回 UNKNOWN,而 INNER JOIN 只保留判断结果为 TRUE 的行。所以只要 ON t1.id = t2.ref_id 中任意一边是 NULL,这一对记录就彻底不参与连接。
- 现象:左表明明有 1000 行,右表也有匹配的
ref_id值,但INNER JOIN结果只有 600 行 - 查因:用
SELECT * FROM t2 WHERE ref_id IS NULL一查,发现 400 行ref_id是空的 - 本质:这不是“漏连”,而是标准行为——
NULL不等于任何东西,包括它自己
LEFT JOIN 里 ON 含 NULL 字段,右表记录照样“消失”
LEFT JOIN 保证左表全量输出,但前提是右表能“连得上”。而 ON 子句里的比较一旦涉及 NULL,结果就是 UNKNOWN,连接失败,右表字段全为 NULL —— 看似连上了,实则没真正匹配。
- 错误写法:
LEFT JOIN t2 ON t1.id = t2.user_id,但t2.user_id允许NULL - 后果:那些
t2.user_id IS NULL的行,不会和任何t1.id匹配,t2字段全为NULL,但你也看不出它们本该属于哪条左表记录 - 补救成本高:后续要用
COALESCE(t2.user_id, t1.id)或t2.user_id IS NOT DISTINCT FROM t1.id才能兜住,可读性和性能都打折
加 NOT NULL 约束比在 SQL 里到处写 COALESCE 更可靠
业务上如果「关联字段为空」本身就不合理(比如订单必须有用户 ID、评论必须有文章 ID),那最干净的做法是在建表时就用 NOT NULL 锁死。
CREATE TABLE orders (id INT PRIMARY KEY, user_id INT NOT NULL, ...)ALTER TABLE comments MODIFY post_id BIGINT NOT NULL;- 好处:数据库层拦截非法插入,避免脏数据入库;查询时不用再为
NULL分支兜底;优化器也更敢下推条件、选择索引 - 反例:靠应用层校验或 SQL 里反复写
WHERE t2.ref_id IS NOT NULL,既容易遗漏,又掩盖了数据模型缺陷
外键约束自带 NOT NULL 效果?不一定
外键列允许为 NULL(这是 SQL 标准),除非你显式加上 NOT NULL。也就是说:FOREIGN KEY (user_id) REFERENCES users(id) 并不阻止 user_id 存 NULL。
- 常见误判:以为加了外键就等于“必填”,结果导出数据时发现大量
user_id IS NULL记录 - 正确姿势:外键 +
NOT NULL要分开写,例如user_id INT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) - 迁移注意:给已有表加
NOT NULL前,必须先清理掉NULL值,否则ALTER TABLE ... MODIFY ... NOT NULL会报错
真正麻烦的不是写 NOT NULL,而是当某张表已经跑了一年、关联字段里混着几百条 NULL,你还得一边修数据一边改 SQL——这时候才明白,约束不是限制,是提前划好的边界。










