普通索引与唯一索引查询性能几乎无差别,差异主要体现在写入性能:唯一索引因需实时校验唯一性而无法使用change_buffer,导致更多磁盘IO;普通索引在写多读少场景更优,且应用层已保障唯一性时无需数据库层UNIQUE约束。
如果你只看 SELECT 性能,普通索引和唯一索引几乎一样快。InnoDB 按数据页(默认 16KB)读取,查到目标记录时,整页通常已在内存里;后续多判断一条“下个记录是否还满足条件”,对 CPU 来说就是一次指针跳转+一次比较——开销可忽略。
唯一索引确实会在找到第一条 k=5 后立刻停止;普通索引会继续往后扫,直到碰到第一个 k≠5 的记录。但只有当目标值恰好落在数据页末尾、且下一条在另一页时,才触发额外磁盘 IO——这种概率极低(千分之一量级),均摊后查性能差不到 0.1%。
更新性能才是关键分水岭。核心在于 change_buffer:当目标数据页不在内存中时,普通索引能把更新先缓存在内存里,等页被读入后再合并(merge);而唯一索引必须校验唯一性,就得先把对应页从磁盘加载进来——相当于绕过了 change_buffer,多了一次随机磁盘读。
change_buffer 能显著降低 IO 压力change_buffer 反而可能增加 merge 开销,此时两者差距缩小,可按语义选innodb_change_buffer_max_size 默认 25(占 buffer pool 25%),可动态调,但对唯一索引无效如果应用层(比如 Java 服务)已通过分布式锁、幂等设计、事务校验等方式确保不会插入重复值(例如身份证号、手机号),那字段本身具备逻辑唯一性,但没必要强加数据库层的 UNIQUE 约束。
原因很实在:
INSERT/UPDATE,尤其高并发写入时,容易成为瓶颈Duplicate entry,常被误判为数据污染或并发 bug典型反例:
ALTER TABLE user ADD INDEX idx_id_card (id_card);而不是
ALTER TABLE user ADD UNIQUE INDEX uk_id_card (id_card);
只有两种刚性场景才该上 UNIQUE:
统无幂等)UNIQUE 或 PRIMARY KEY)注意:PRIMARY KEY 本质是 NOT NULL + UNIQUE,但每张表只能一个;而 UNIQUE 索引允许多个,也允许含 NULL(单个 UNIQUE 列可存多个 NULL,这是 MySQL 的行为)。
真正卡住人的从来不是“查得快不快”,而是半夜扩容时发现写入 QPS 掉了一半——回头一看,几十个本可普索引的字段全建了唯一索引。约束越重,代价越实;能靠代码守的界,就别让数据库替你扛。