乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 数据库基础与安装

    • MySQL 简介
    • MySQL 5.6 到 5.7 到 8.0 关键差异速查
    • 安装 MySQL 5.7
    • 连接与断开服务器
    • 创建数据库
    • 创建数据表
    • 数据库与数据表
    • 加载数据
    • 获取数据库信息
    • 批处理模式
    • SHOW 语句汇总
    • FLUSH 与 RESET 语句
    • my.cnf 核心参数
    • 字符集与排序规则
  • 第2章 SQL基础查询

    • SELECT
    • WHERE
    • ORDER BY
    • LIMIT
    • COUNT
    • 聚合函数
    • 比较运算符
    • 逻辑运算符
    • 算术运算符
    • 模式匹配
    • NULL 值处理
    • UPDATE
    • DELETE
    • REPLACE
    • SELECT INTO
  • 第3章 数据类型与运算符

    • 数值类型
    • 字符串类型
    • 日期时间类型
    • BIT 类型
    • ENUM 类型
    • SET 类型
    • JSON 类型
    • 类型转换
  • 第4章 函数与表达式

    • 字符串函数
    • 数值函数
    • 日期函数
    • 全文检索函数
  • 第5章 高级查询与子查询

    • JOIN
    • 子查询
    • UNION
    • GROUP BY
    • HAVING
    • DISTINCT
  • 第6章 表与索引

    • 数据定义语言
    • 修改表结构
    • 视图
    • 修改视图与检查选项
    • 外键
    • 索引
    • 唯一索引
    • 复合索引
    • 存储引擎对比
    • 分区表
    • 第一范式与第二范式
    • 第三范式与 BC 范式
    • 反范式设计
  • 第7章 存储过程与函数

    • 存储过程
    • 存储函数
    • 变量
    • 流程控制
    • 游标
    • 预处理语句
  • 第8章 事务与锁

    • 事务
    • 事务隔离级别
    • 锁机制
    • MVCC
    • 死锁专题分析
    • LOCK TABLES
    • XA 事务
  • 第9章 用户管理与安全

    • 用户管理
    • 权限管理
    • 角色
    • SQL 注入防范
  • 第10章 性能优化入门

    • 执行计划
    • 索引优化
    • 查询优化
    • 查询优化器提示
    • 慢查询日志
    • InnoDB 深入机制
    • InnoDB 专项优化
    • Performance Schema
    • sys Schema
  • 第11章 复制与高可用

    • 主从复制原理
    • 半同步复制配置
    • binlog 开启与 point-in-time 恢复
    • mysqldump 全库备份
    • mysqldump 单表与条件备份
    • mysqldump 恢复与导入
    • xtrabackup 全量热备
    • xtrabackup 准备与恢复
    • xtrabackup 增量与流式备份
  • 第12章 触发器与事件

    • 触发器
    • 事件调度器
  • 参考

    • MySQL 5.7 专业术语大全
    • MySQL 5.7 关键字与保留字大全

唯一索引

导学

业务中经常需要确保某列的值不重复——如员工姓名、工号等。唯一索引不仅提供了这种约束,还能像普通索引一样加速查询。

定义

唯一索引(Unique Index):一种特殊的索引,它在加速查询的同时,强制保证索引列的值在全表中唯一。NULL 值在唯一索引中比较特殊:MySQL 允许多个 NULL 值存在(因为 NULL <> NULL)。

创建方式

方式一:CREATE UNIQUE INDEX

当前数据状态:创建员工表

CREATE TABLE employees (
    emp_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept VARCHAR(20),
    score DECIMAL(5,2)
);

INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);

SELECT * FROM employees;
emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

操作语句:

-- 为 emp_name 列创建唯一索引
CREATE UNIQUE INDEX uk_emp_name ON employees(emp_name);

SHOW INDEX FROM employees;

操作后数据状态(节选):

TableNon_uniqueKey_nameSeq_in_indexColumn_name
employees0PRIMARY1emp_id
employees0uk_emp_name1emp_name

结果解读:Non_unique=0 表示这是唯一索引。现在 emp_name 列不能有重复值。

方式二:建表时声明

CREATE TABLE members (
    member_id INT PRIMARY KEY AUTO_INCREMENT,
    emp_name VARCHAR(20),
    dept VARCHAR(20),
    UNIQUE KEY uk_name (emp_name),
    UNIQUE KEY uk_dept (dept)
);

SHOW INDEX FROM members;
TableNon_uniqueKey_nameSeq_in_indexColumn_name
members0PRIMARY1member_id
members0uk_name1emp_name
members0uk_dept1dept

结果解读:建表时通过 UNIQUE KEY 可以同时创建多个唯一索引。member_id 是主键(特殊的唯一索引),emp_name 和 dept 也分别被唯一索引约束。

方式三:ALTER TABLE ADD CONSTRAINT UNIQUE

当前数据状态:employees 表已有 uk_emp_name。

操作语句:

ALTER TABLE employees ADD CONSTRAINT uk_dept UNIQUE (dept);

SHOW INDEX FROM employees;

操作后数据状态:employees 表现在有 uk_emp_name 和 uk_dept 两个唯一索引。

在 MySQL 中,UNIQUE 约束和 UNIQUE INDEX 在底层实现上是完全相同的——都是唯一索引。

SQL 示例

场景一:唯一索引阻止重复插入

当前数据状态:employees 表已有数据

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

操作语句:

-- 尝试插入重复的 emp_name
INSERT INTO employees (emp_name, dept, score) VALUES ('大翔', '产品部', 90);

操作后数据状态:

ERROR 1062 (23000): Duplicate entry '大翔' for key 'uk_emp_name'
SELECT * FROM employees;
emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

结果解读:由于 emp_name 列有唯一索引,'大翔' 已存在,插入被拒绝。表中数据没有变化。

场景二:处理重复插入(INSERT IGNORE)

当前数据状态:同上。

操作语句:

-- 使用 INSERT IGNORE 忽略重复错误
INSERT IGNORE INTO employees (emp_name, dept, score) VALUES ('大翔', '产品部', 90);

SELECT * FROM employees;

操作后数据状态:

emp_idemp_namedeptscore
1大翔技术部100
2白歌技术部NULL

结果解读:INSERT IGNORE 遇到唯一键冲突时静默跳过,不报错,也不插入数据。返回 0 rows affected。适用于"插入一条记录,如果已存在则忽略"的场景。

场景三:重复时更新(ON DUPLICATE KEY UPDATE)

当前数据状态:employees 表同上。

操作语句:

-- 重复时更新 dept 和 score
INSERT INTO employees (emp_name, dept, score)
VALUES ('大翔', '产品部', 90)
ON DUPLICATE KEY UPDATE dept = '产品部', score = 90;

SELECT * FROM employees;

操作后数据状态:

emp_idemp_namedeptscore
1大翔产品部90
2白歌技术部NULL

结果解读:ON DUPLICATE KEY UPDATE 遇到唯一键冲突时执行 UPDATE 部分。大翔 的 dept 从 技术部 更新为 产品部,score 从 100 更新为 90。这实现了"存在则更新、不存在则插入"的 Upsert 语义。

场景四:NULL 值的特殊性

当前数据状态:创建新表测试 NULL

CREATE TABLE null_test (
    id INT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(20) UNIQUE
);

操作语句:

-- 插入多个 NULL
INSERT INTO null_test (code) VALUES (NULL);
INSERT INTO null_test (code) VALUES (NULL);
INSERT INTO null_test (code) VALUES (NULL);

SELECT * FROM null_test;

操作后数据状态:

idcode
1NULL
2NULL
3NULL

结果解读:三条记录都插入成功!在 SQL 标准中,NULL 表示未知,任何值与 NULL 的比较结果都是 UNKNOWN。因此 NULL <> NULL 的结果是 UNKNOWN 而非 TRUE,多个 NULL 不违反唯一性约束。

场景五:唯一索引加速查询

当前数据状态:employees 表有唯一索引 uk_emp_name。

操作语句:

-- 按 emp_name 查询
SELECT * FROM employees WHERE emp_name = '白歌';

操作后数据状态:

emp_idemp_namedeptscore
2白歌技术部NULL

结果解读:唯一索引和普通索引一样,能加速查询。由于唯一索引保证列值不重复,优化器知道匹配到一条记录后就可以停止搜索,因此效率比普通索引更高。

主键 vs 唯一索引

特性主键(PRIMARY KEY)唯一索引(UNIQUE)
唯一性✅✅
允许 NULL❌✅(允许多个)
每张表数量只能有 1 个可以有多个
聚簇索引是否
外键引用可以被引用可以被引用(5.7+)

对比示例:

CREATE TABLE pk_vs_uk (
    id INT PRIMARY KEY,           -- 主键:不允许 NULL,聚簇索引
    passport VARCHAR(20) UNIQUE,  -- 唯一索引:允许 NULL,二级索引
    id_card VARCHAR(18) UNIQUE    -- 唯一索引:允许 NULL,二级索引
);

-- 主键不能为 NULL
INSERT INTO pk_vs_uk (id, passport, id_card) VALUES (NULL, 'P123', 'I456');
-- ERROR 1048: Column 'id' cannot be null

-- 唯一索引可以为 NULL
INSERT INTO pk_vs_uk (id, passport, id_card) VALUES (1, NULL, 'I456');
INSERT INTO pk_vs_uk (id, passport, id_card) VALUES (2, NULL, 'I789');
-- 两条都成功!

SELECT * FROM pk_vs_uk;
idpassportid_card
1NULLI456
2NULLI789

常见误区

误区正解
唯一索引就是主键不是。主键更严格(不允许 NULL),且只有一个。
唯一索引不能有 NULL可以,且允许多个 NULL。
UNIQUE 约束和 UNIQUE INDEX 不同在 MySQL 中完全相同,底层都是唯一索引。

面试考点

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

主键不允许 NULL 且每张表只能有一个,同时是聚簇索引;唯一索引允许 NULL(多个),每张表可有多个,是二级索引。

Q:唯一索引中为什么可以有多个 NULL?

因为 SQL 的三值逻辑规定 NULL <> NULL 的结果是 UNKNOWN 而非 TRUE,所以多个 NULL 不违反唯一性约束。

Q:INSERT IGNORE 和 ON DUPLICATE KEY UPDATE 的区别?

INSERT IGNORE 遇到唯一键冲突时忽略错误,不插入也不更新;ON DUPLICATE KEY UPDATE 遇到冲突时执行指定的 UPDATE 操作,实现"存在则更新、不存在则插入"。

小结

  • 唯一索引保证列值唯一,同时加速查询
  • 允许多个 NULL 值存在
  • INSERT IGNORE 和 ON DUPLICATE KEY UPDATE 是处理唯一冲突的利器
  • 主键是特殊的唯一索引,更严格(不允许 NULL,只能有一个)

下一章引子:单列索引不够用,多列组合查询需要多列索引——复合索引。

上一页
索引
下一页
复合索引