唯一索引和主键索引的区别?

#中间件#面试八股

唯一索引和主键索引的区别?

结论:主键索引是「唯一 + 非空 + 聚簇 + 全表仅一个」的索引,它决定了数据在磁盘上的物理顺序;唯一索引是二级索引上的唯一约束,可以建多个、允许 NULL(多个 NULL 不算重复)。一句话:主键是表的身份,唯一索引是业务规则。

一、结构上的位置不同

                 ┌─────────────────────────────┐
   聚簇索引       │ 主键 B+Tree                  │
   (只能一个)    │  叶子 = 主键值 + 完整行数据   │
                 └─────────────────────────────┘
                            ▲
                 ┌──────────┴──────────────────┐
   二级索引       │ 唯一索引 uk_phone (phone)    │
   (可以多个)    │  叶子 = phone + 主键值        │
                 └─────────────────────────────┘

查 WHERE phone = ? 走唯一索引拿到主键,再回表;查 WHERE id = ? 一次 B+Tree 就结束。这是两者在性能上最本质的差别。

二、逐项对照

维度主键索引唯一索引
数量每表最多一个可以有多个
NULL不允许允许,且多行 NULL 不冲突
索引类型聚簇索引二级索引(需回表)
默认创建有主键自动创建;无主键则用首个非空唯一索引需显式 UNIQUE KEY
外键引用可被引用也可被引用(需唯一)
值可否修改可以,但代价极高可以,只影响本索引
冲突错误10621062
典型用途行标识、聚簇组织手机号、邮箱、订单号去重

三、NULL 语义:唯一索引的常见陷阱

CREATE TABLE t_user (
  id    BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  phone VARCHAR(20) DEFAULT NULL,
  email VARCHAR(64) DEFAULT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uk_phone (phone)
) ENGINE=InnoDB;

-- 两行都能插入成功:NULL 不参与唯一性比较
INSERT INTO t_user (phone) VALUES (NULL), (NULL);

-- 这一行会失败:非 NULL 值重复
INSERT INTO t_user (phone) VALUES ('13800000000');
INSERT INTO t_user (phone) VALUES ('13800000000');
-- ERROR 1062 (23000): Duplicate entry '13800000000' for key 't_user.uk_phone'

业务上如果要求「手机号必须唯一且必填」,就该把它定义成 NOT NULL,否则唯一约束会被 NULL 绕开。

四、查询性能与写入代价

  • 等值查询:主键命中即返回整行;唯一索引命中后还要一次回表。若查询列全在索引内,唯一索引也能做覆盖索引。
  • 范围查询:主键范围扫描是聚簇索引顺序读,页内连续;唯一索引范围扫描先扫索引再逐行回表,随机 I/O 明显更多。
  • 写入:每多一个唯一索引,插入就要多维护一棵 B+Tree,并做一次唯一性检查。唯一索引越多,写入越慢、死锁概率越高。
  • 并发插入:唯一索引上的并发插入在 RR 隔离级别下依赖间隙锁保证唯一性,重复键冲突时会加 next-key lock,热点键容易形成锁等待。
-- 查看表上所有索引及唯一性
SHOW INDEX FROM t_user;
-- 或
SELECT index_name, non_unique, column_name, seq_in_index
FROM information_schema.statistics
WHERE table_schema = 'demo' AND table_name = 't_user';

五、主键选型的三条经验

  1. 用与业务无关的自增或雪花 ID:业务键(手机号、身份证)一旦变更,聚簇索引要整表重排,所有二级索引存的主键值也要跟着改。
  2. 主键尽量短:每个二级索引叶子都冗余主键值,BIGINT 比 CHAR(32) 的 UUID 省下的空间会乘以索引数量。
  3. 自增主键让插入集中在 B+Tree 右端,页分裂少;随机主键会在中间反复分裂,碎片率高。

常见追问

  • 没有主键的表会怎样? InnoDB 会选第一个非空唯一索引做聚簇索引,都没有就生成隐藏的 6 字节 ROW_ID,此时无法用主键定位行。
  • 唯一索引能当主键用吗? 可以 ALTER TABLE t ADD PRIMARY KEY (col),前提是该列非空且当前无重复;它会把表重建为以该列聚簇。
  • 主键和唯一索引哪个查询更快? 单行等值、需要整行时主键更快;只要索引列时两者都能覆盖,差距很小。
  • INSERT IGNORE 和 ON DUPLICATE KEY UPDATE 的区别? 前者静默跳过冲突行,后者转为更新;两者都依赖唯一性约束识别冲突,且都会消耗自增值。
  • 唯一索引能保证「先查后插」的并发安全吗? 能,靠数据库的唯一约束兜底;应用层「先 SELECT 再 INSERT」在并发下必然漏判,必须依赖约束或分布式锁。

小结

主键索引决定物理存储,唯一索引只增加一条业务规则。选主键看「稳定、短、单调」,加唯一索引看「是否真的需要数据库兜底去重」,因为每一个唯一索引都在为写入路径增加成本。

笔记加载中…