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

主键索引:一种特殊的唯一索引,每张表只能有一个,用于唯一标识每一行记录,InnoDB 中主键是 聚簇索引

唯一索引:保证索引列的值唯一,一张表可以有多个唯一索引,InnoDB 中唯一索引是 二级索引(非聚簇索引)

核心区别对比

对比维度主键索引(PRIMARY)唯一索引(UNIQUE)
数量限制 每表 只能有 1 个 每表 可以有多个
NULL 值 不允许 NULL 允许 NULL(但最多 1 个)
存储结构 聚簇索引,叶子节点存完整数据 二级索引,叶子节点存主键值
索引回表 不需要回表 需要回表(查询非索引列时)
创建语法 PRIMARY KEY (col) UNIQUE KEY uk_name (col)
是否必须 建议有,但不强制 按需创建

一句话总结:主键是 唯一的聚簇索引,不允许 NULL;唯一索引是 可以有多个的非聚簇索引,允许 NULL。

深度解析

一、存储结构差异:聚簇索引 vs 二级索引

主键索引和唯一索引在 InnoDB 中的存储结构完全不同:

上图对比了主键索引和唯一索引的存储结构。关键区别在于:

  • 主键索引(聚簇索引):叶子节点直接存储 完整的行数据,通过主键查询可以直接获取所有字段,无需回表
  • 唯一索引(二级索引):叶子节点只存储 索引列的值 + 主键值,如果查询其他字段,需要拿着主键值回表查询聚簇索引

二、NULL 值处理差异

这是面试中的高频考点:

-- 创建测试表
CREATE TABLE user (
    id BIGINT PRIMARY KEY,           -- 主键,不允许 NULL
    email VARCHAR(50) UNIQUE,        -- 唯一索引,允许 NULL
    phone VARCHAR(20) UNIQUE         -- 唯一索引,允许 NULL
);

-- 主键测试:插入 NULL 会报错
INSERT INTO user (id, email) VALUES (NULL, 'test@qq.com');
-- ❌ ERROR 1048: Column 'id' cannot be null

-- 唯一索引测试:允许 NULL,且 MySQL 中可以插入多个 NULL
INSERT INTO user (id, email) VALUES (1, NULL);      -- ✅ 成功
INSERT INTO user (id, email) VALUES (2, NULL);      -- ✅ 成功(MySQL 认为多个 NULL 不重复)
INSERT INTO user (id, email) VALUES (3, 'a@qq.com'); -- ✅ 成功
INSERT INTO user (id, email) VALUES (4, 'a@qq.com'); -- ❌ Duplicate entry

关键结论

  • 主键:绝对不允许 NULL,这是主键的基本约束
  • 唯一索引:允许 NULL,且 MySQL 中可以插入多个 NULL 值(因为 NULL != NULL

三、查询性能差异:回表问题

通过主键查询 vs 通过唯一索引查询的性能差异:

-- 表结构
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    email VARCHAR(50) UNIQUE,
    name VARCHAR(50),
    age INT
);

-- 场景 1:通过主键查询
SELECT * FROM user WHERE id = 1;
-- ✅ 直接走聚簇索引,一次查询即可获取完整数据

-- 场景 2:通过唯一索引查询所有字段
SELECT * FROM user WHERE email = 'test@qq.com';
-- ⚠️ 需要回表:先查唯一索引得到 id,再回表查聚簇索引

-- 场景 3:通过唯一索引查询索引列(覆盖索引)
SELECT email FROM user WHERE email = 'test@qq.com';
-- ✅ 覆盖索引,不需要回表

四、使用场景建议

场景推荐索引类型原因
标识每一行记录 主键索引 每表必须有唯一标识,推荐自增 BIGINT
用户邮箱不能重复 唯一索引 业务唯一性约束,允许未设置邮箱(NULL)
手机号唯一 唯一索引 允许用户暂未绑定手机号
身份证号唯一 唯一索引 允许 NULL(可能未录入)
联合唯一(用户 + 日期) 联合唯一索引 UNIQUE KEY uk_user_date (user_id, date)

最佳实践

-- 推荐:使用 BIGINT 自增主键
CREATE TABLE orders (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    order_no VARCHAR(32) UNIQUE NOT NULL,  -- 业务订单号,不允许 NULL
    user_id BIGINT NOT NULL,
    INDEX idx_user (user_id)
);

-- 不推荐:没有主键的表
-- MySQL 会自动选择一个非空唯一索引作为聚簇索引
-- 如果没有合适的,会生成一个隐藏的 6 字节主键

五、如果没有主键会怎样?

InnoDB 要求每张表必须有聚簇索引:

posted on 2026-05-06 09:46  小陶coding  阅读(0)  评论(0)    收藏  举报