唯一索引强制触发磁盘随机读,绕过change buffer,因需先校验唯一性而必须将索引页从磁盘读入内存;null值使唯一约束失效;应用层已幂等时加唯一索引属冗余。

唯一索引强制触发磁盘随机读,绕过 Change Buffer
普通索引写入时能缓存变更到 change_buffer,而唯一索引必须先校验唯一性——这意味着它得把目标索引页从磁盘读进内存,哪怕那页当前完全不在 buffer_pool 里。这个“先读再判”的动作彻底绕过了 change_buffer,每次写都多一次随机 I/O。
常见现象包括:innodb_buffer_pool_reads 突增、INSERT ... ON DUPLICATE KEY UPDATE 执行耗时明显升高、慢日志里频繁出现单条写入延迟超 100ms。
- 机械硬盘(HDD)下影响更剧烈;SSD 上虽延迟低,但高并发离散写仍会拖累吞吐
-
innodb_change_buffering对唯一索引完全无效,设成all或inserts都不改变行为 - 即使你调大了
innodb_change_buffer_max_size,它也只对普通索引起作用
NULL 值让唯一性约束形同虚设
MySQL 中 UNIQUE 索引允许任意多个 NULL,因为 NULL != NULL。这导致业务以为“加了唯一索引就绝不会重复”,结果插入 10 条 email IS NULL 全部成功。
检查方式很简单:SHOW CREATE TABLE t;,重点看约束定义是否含 UNIQUE NOT NULL。如果只是 UNIQUE 而没加 NOT NULL,那这个“唯一”在空值场景下根本不存在。
- 联合唯一索引中,全为
NULL的行也被视为可重复 - 应用层若按“非空才校验”设计,数据库层的
UNIQUE实际只覆盖部分数据,容易漏防
应用层已做幂等,再加唯一索引是双输
如果 Java 服务已用分布式锁 + 幂等表 + 事务确保 id_card 不重复,那再建 UNIQUE INDEX uk_id_card (id_card) 就纯属冗余:既拖慢写入,又抬高死锁概率,还让错误更难定位。
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
Duplicate entry 'xxx' for key 'uk_id_card' 这类报错常被误判为数据污染或并发 bug,实际只是冗余校验撞上了应用层已拦住的重复请求。
- 真正需要
UNIQUE的只有两类刚性场景:外键引用列、或多系统直连 DB 且无法统一幂等 - 高频写入字段(如日志表的
trace_id)优先选普通索引;去重要求由异步任务或应用兜底 - 已有普通索引想升级为唯一索引前,务必先跑
SELECT COUNT(*) - COUNT(DISTINCT col)检查隐性重复
查询性能几乎没差别,别为“快一点”选唯一索引
执行 SELECT id FROM user WHERE id_card = 'xxx' 时,唯一索引找到第一条就停,普通索引多扫一条确认无重复——但这只是内存里一次指针跳转+一次值比较,CPU 开销几纳秒,均摊后查性能差不到 0.1%。
真正影响查询的是回表开销:主键索引(聚簇索引)叶子节点存整行数据,UNIQUE 和普通索引都是二级索引,叶子节点只存索引列+主键值,查完还得回表一次。
- 优化器对唯一索引的
type: const或type: eq_ref提示,只在等值查询且确定单行返回时生效,不是普遍提速 - 别因“语义上应该唯一”就默认加
UNIQUE——业务逻辑唯一 ≠ 数据库层必须强制唯一
唯一索引的核心代价藏在写路径里,而不是名字里那个“唯一”;最容易被忽略的,其实是 NULL 值和应用层幂等之间的边界模糊。










