缺少not null约束本身不直接导致性能下降,但会放大索引缺失、类型不匹配和null泛滥等问题,使join退化为全表扫描、嵌套循环及临键锁,优化器因语义不确定而弃用索引;not null与索引必须协同才构成join性能的最小可靠单元。

缺少 NOT NULL 约束本身不直接导致性能下降,但它会放大其他问题——尤其是当它和索引缺失、类型不匹配、NULL值泛滥同时出现时,JOIN 就会从“快查”变成“锁表+全扫+兜底判断”的组合拳。
为什么JOIN字段允许NULL会让优化器不敢用索引
MySQL 和 PostgreSQL 的优化器在面对可能含 NULL 的关联字段时,会主动放弃某些索引策略。比如:user_id 是 INT 但没加 NOT NULL,即使建了索引,优化器也可能认为“NULL = NULL 是否成立”会影响连接语义,从而退化为嵌套循环(NLJ)而非更高效的哈希连接或索引查找。
- EXPLAIN 中看到
type: ALL或key: NULL,且rows高得离谱,大概率是这个原因 - 外键列默认允许
NULL——FOREIGN KEY (user_id) REFERENCES users(id)并不阻止插入user_id IS NULL记录 - 一旦 JOIN 条件中某侧出现大量
NULL,数据库还得额外做三值逻辑判断(TRUE/FALSE/UNKNOWN),CPU 开销上升,执行计划也更难预测
NULL值泛滥 + 缺少索引 = 逻辑锁表
在 REPEATABLE READ 隔离级别下,JOIN 字段无索引会导致全表扫描;而全表扫描会触发 InnoDB 的临键锁(Gap Lock + Record Lock),把整个主键间隙都锁住。如果该字段又允许 NULL,那扫描范围往往更大——因为 NULL 在 B+ 树中被特殊处理,可能落在最左或最右间隙,进一步扩大锁覆盖范围。
- 现象:
UPDATE t1 JOIN t2 ON t1.id = t2.ref_id执行缓慢,SHOW ENGINE INNODB STATUS显示一堆X lock on gap - 根本原因不是“有NULL”,而是“有NULL + 没索引”让优化器无法跳过无效行,只能硬扫
- LEFT JOIN 场景更危险:若右表
ON字段含大量NULL,又没索引,就会对右表每一条(包括NULL行)都尝试匹配,锁住所有扫描路径
NOT NULL + 索引才是JOIN性能的最小可靠单元
单有索引不够,单有 NOT NULL 也不够。二者必须一起出现,才能让优化器敢做确定性决策。例如:orders.user_id 是 INT,但未设 NOT NULL,即使你给它加了索引,只要业务写入时混入了几十万条 user_id IS NULL,查询时仍可能因统计信息失真(NULL 值占比过高)被优化器弃用该索引。
- 建索引前先确认字段是否已加
NOT NULL:用SHOW CREATE TABLE orders查看user_id定义 - 补约束要谨慎:已有数据含
NULL时,ALTER TABLE ... MODIFY user_id INT NOT NULL会失败,得先UPDATE ... SET user_id = 0 WHERE user_id IS NULL(注意 0 是否业务合法值) - 联合索引场景下,
NOT NULL对最左前缀生效至关重要:比如索引是(status, user_id),但user_id允许NULL,而查询只用user_id = ?,该索引就完全失效
真正容易被忽略的点是:NOT NULL 不是“锦上添花”的设计装饰,而是 JOIN 能否稳定走索引的语义前提。一旦放任 NULL 存在,后续所有索引、类型对齐、执行计划调优,都像在流沙上盖楼。











