优先选普通索引,除非业务强依赖唯一性约束校验;唯一索引更新时无法使用Change Buffer,因需强制校验冲突而必须读入数据页,而普通索引可缓存更新操作以减少随机读IO,查询性能差异则微乎其微。

优先选普通索引,除非业务强依赖唯一性约束校验。
唯一索引在更新时无法使用 Change Buffer
Change Buffer 只对非唯一二级索引生效。只要索引带 UNIQUE 约束,InnoDB 每次写入前都必须校验是否存在冲突,这就强制要求把对应的数据页加载进内存——否则没法判断“有没有重复”。一旦数据页进了内存,后续更新直接操作内存页,change buffer 就没机会介入了。
而普通索引没有这个校验步骤,当目标页不在内存中时,InnoDB 会把 INSERT/UPDATE/DELETE 对二级索引的变更暂存到 change buffer,等该页后续被读取或后台线程触发 merge 时再批量应用。这省掉了一次随机磁盘读,对写密集场景提升明显。
- 典型表现:写入吞吐量下降、
innodb_buffer_pool_reads显著升高 - 配置影响:
innodb_change_buffer_max_size默认 25%,最大可调至 50%,但只对普通索引起作用 - 注意:主键索引(聚簇索引)不走 change buffer,无论是否唯一
查询性能差异几乎可以忽略
不管是普通索引还是唯一索引,B+ 树查找都落到同一个数据页里。查到第一个匹配记录后,唯一索引立刻停,普通索引多走一次“下一条”判断——但这只是内存里的指针跳转和一个比较操作,CPU 几个周期就完成了。
只有极端情况(比如目标值恰好是数据页最后一个 key)才会触发跨页读取,而整型字段一个 16KB 数据页能存近 1000 个 key,这种概率低到在平均性能计算中直接归零。
- 实际观察:
SELECT ... WHERE id_card = ?这类等值查询,两种索引的Handler_read_next差异微乎其微 - 别被“唯一索引提前退出”误导——B+ 树叶子节点是双向链表,遍历下一条成本极低
- 真正影响查询性能的是索引字段长度、是否覆盖、数据分布,不是“唯一”与否
业务已保证不重复时,唯一索引纯属冗余开销
如果你的代码层已经确保不会插入重复的 id_card 或 phone,那数据库层的唯一性检查就是重复劳动。它不仅让每次写入失去 change buffer 优化,还额外消耗 CPU 做哈希比对或 B+ 树搜索,同时增加死锁风险(多个事务争抢同一索引页做唯一校验)。
反过来,如果某字段确实需要强一致性保障(比如发券系统中的 order_id 防重),那就必须用唯一索引——这时牺牲写性能是必要代价。
- 常见误用:给
user_id加唯一索引,但该字段本就是主键,再加一层唯一索引毫无意义 - 更隐蔽的问题:联合索引中把高频更新字段放在前面,又加了
UNIQUE,结果所有更新都被迫读页 - 验证方法:打开
innodb_change_buffering = all后对比SHOW ENGINE INNODB STATUS中的change buffer统计项
真正关键的不是“选哪个索引”,而是想清楚:你到底要靠数据库兜底,还是靠应用逻辑控制?后者更轻、更快;前者更稳、更重。Change Buffer 是给“信得过的普通索引”准备的加速器,不是给唯一约束擦屁股的补丁。











