普通索引和唯一索引查询性能几乎无差异,核心区别在写入:唯一索引因需校验唯一性而强制磁盘io,无法使用change_buffer;普通索引可缓存变更提升写吞吐,但放弃数据库层唯一性保障。

查得快不快,其实没区别
普通索引和唯一索引在 SELECT 查询时的性能差异几乎可以忽略。比如执行 SELECT id FROM user WHERE id_card = '11010119900307271X',两者都走 B+ 树查找,定位到数据页后,靠二分法找记录——唯一索引找到第一个就停;普通索引会多看一条,确认是否重复。但因为 InnoDB 是按页(默认 16KB)加载数据,同一页里通常有几百上千个索引项,「多看一次」只是内存里一次指针跳转+一次值比较,CPU 几纳秒就完了。
常见错误现象:Duplicate entry 'xxx' for key 'uk_id_card' 这不是查询慢,是写入被拦住了——说明你建了唯一索引,而应用层又没处理好冲突逻辑。
- 别为“查得更快”选唯一索引——它不提速
- 别因“业务已校验”就放弃唯一索引——代码会漏,数据库不会
- 如果字段天然唯一(如身份证、手机号、订单号),且业务语义要求“绝不能重复”,那就必须用
UNIQUE
写得慢不慢,差别真在磁盘 IO 上
关键差异藏在更新路径里:普通索引能用 change_buffer,唯一索引不能。
当你要插入或更新一条记录,而目标索引页不在内存(buffer_pool)中时:
- 普通索引:把变更先记进
change_buffer,等后续该页被读入内存时再合并——减少随机磁盘 IO - 唯一索引:必须先把对应索引页从磁盘读进来,才能判断是否违反唯一性——强制一次磁盘 IO
这意味着高并发写入场景下(比如批量导入用户、日志落库),普通索引的写吞吐可能明显更高。但代价是:你放弃了数据库层的最终一致性保障。
实操建议:SET GLOBAL innodb_change_buffer_max_size = 50; 可调大 change buffer 占比,但别设 100%——它会挤占 buffer_pool 空间,影响查询缓存。
NULL 值是个隐藏陷阱
MySQL 中,UNIQUE 索引允许任意多个 NULL,因为 NULL != NULL。这常导致线上数据“看似唯一,实则失控”。
比如给 email 字段加了 UNIQUE,但没加 NOT NULL,结果插入 10 条 email IS NULL 的用户——全成功,业务却以为邮箱必填且唯一。
- 如果业务语义是“该字段必须有值且唯一”,必须同时声明:
UNIQUE NOT NULL - 如果允许为空,但非空时必须唯一,仍需
UNIQUE,但应用层要额外兜底空值逻辑 - 用
SHOW CREATE TABLE t;检查实际约束,别只看索引名
主键、唯一索引、普通索引,到底谁管什么
主键是特殊的唯一索引(PRIMARY KEY),但强制 NOT NULL 且一张表只能一个;唯一索引可多个,可含 NULL;普通索引只加速,不保数据。
典型误用:ALTER TABLE user ADD INDEX idx_mobile(mobile); ——明明手机号必须唯一,却只建普通索引,结果脏数据越积越多,DBA 每月都要人工去重。
- 身份证、手机号、设备 ID、邀请码这类强唯一标识,优先建
UNIQUE索引 - 状态字段(如
status)、时间范围字段(如created_at)适合普通索引 - 不要用唯一索引替代业务校验——它防不了逻辑错,只防得了数据错
最常被忽略的一点:唯一索引失效往往不是语法问题,而是你忘了它对 NULL 的宽容,或者没意识到它让 change_buffer 失效——这两处,线上出过太多慢写和重复数据事故。










