普通索引可用change_buffer而唯一索引不能,根本原因在于唯一索引写入时必须实时校验唯一性,需先加载数据页到内存逐行比对,无法延迟合并,导致随机读增多、吞吐下降、并发瓶颈及死锁风险上升。

普通索引能用 change_buffer,唯一索引不能
根本原因在于写入时是否需要实时校验唯一性。普通索引不关心值重不重复,InnoDB 允许把 INSERT/UPDATE 对二级索引的修改暂存在 change_buffer 中,等对应数据页被读入内存或后台线程合并(merge)时再批量应用;而唯一索引必须先加载目标页到内存,逐行比对确认无冲突才能写入——这直接绕过了 change_buffer,强制触发一次随机磁盘读。
实操中你可能看到:innodb_buffer_pool_reads 突增、写吞吐下降、INSERT ... ON DUPLICATE KEY UPDATE 耗时飙升。这些都不是配置问题,是机制使然。
唯一索引的“查重”动作在高并发下容易成瓶颈
每次写入都要走完整 B+ 树搜索路径去定位已有记录,这个过程本身就要争抢索引页 latch,还可能引发锁等待甚至死锁。尤其当多个事务同时插入相似值(比如按时间戳前缀分片的 trace_id),热点页竞争会更明显。
- 普通索引写入:缓存变更 → 后续 merge → 单次 I/O 批量落地
- 唯一索引写入:加载页 → 搜索 → 校验 → 写入 → 可能刷脏页 → 多次离散 I/O
机械硬盘(HDD)上差异巨大;SSD 上延迟低,但高并发离散写仍会拖慢整体吞吐。
NULL 值让唯一索引的“唯一性”不可靠
MySQL 规定 UNIQUE 索引允许任意多个 NULL(因为 NULL != NULL)。如果你建了 UNIQUE INDEX uk_email (email) 却没加 NOT NULL,那插入 10 条 email IS NULL 全部成功——业务以为字段必填且唯一,实际已失控。
检查方式:SHOW CREATE TABLE t,看约束是否含 UNIQUE NOT NULL。普通索引没这个问题,它本来就不承诺唯一性。
应用层已做幂等,再加唯一索引是双输
如果 Java 服务已用分布式锁 + 幂等表 + 事务校验确保 id_card 不重复,数据库再加 UNIQUE INDEX uk_id_card (id_card) 就纯属冗余:
- 写入性能损失:失去
change_buffer加速能力 - 错误提示难定位:
Duplicate entry 'xxx' for key 'uk_id_card'容易被误判为数据污染或并发 bug - 死锁概率上升:多个事务争抢同一索引页做唯一校验
真正需要 UNIQUE 的只有两种刚性场景:外键引用列,或多套系统直连 DB 且无法统一幂等——其他情况,优先选普通索引。


















