MySQL 唯一约束和 NULL 的关系

FreeGuideOnline 最新 2026-07-05

什么是唯一约束

唯一约束(UNIQUE Constraint)用于保证表中一列或多列组合的值在全表范围内不重复。它和主键约束(PRIMARY KEY)相似,但有两个关键区别:一张表只能有一个主键,但可以有多个唯一约束;主键不允许 NULL 值,而唯一约束列允许存放 NULL

唯一约束在底层通过创建唯一索引来实现。当你定义 UNIQUE 时,MySQL 自动生成一个同名的唯一索引,利用 B+Tree 结构快速判断值是否已存在。

CREATE TABLE user (
    id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE,
    phone VARCHAR(20) UNIQUE
);

该设计中 emailphone 都不能有重复值,但它们都可以为 NULL

NULL 在数据库中的特殊性

NULL 表示“未知”或“缺失”,它不是零,也不是空字符串。在 SQL 标准中,任何值与 NULL 比较的结果既不是 TRUE 也不是 FALSE,而是 UNKNOWN。因此 NULL = NULL 的结果不是真,而是 NULL

MySQL 使用 IS NULLIS NOT NULL 来检测空值,普通的等值比较无法匹配 NULL。这一特性直接决定了唯一约束如何对待 NULL

唯一约束与 NULL 的核心关系

在 MySQL 中,唯一约束允许多个 NULL 值同时存在。

原因很简单:唯一索引判断重复时,使用的是“值相等”逻辑。由于 NULL 与任何值(包括另一个 NULL)比较都不相等,引擎认为每一个 NULL 都是“不同”的,因此不会违反唯一性。

这一行为在 InnoDBMyISAM 等存储引擎中表现一致,并且符合 SQL-99 标准。

单列唯一约束中的 NULL 行为

创建测试表

CREATE TABLE product (
    id INT AUTO_INCREMENT PRIMARY KEY,
    sku VARCHAR(50) UNIQUE,
    name VARCHAR(100) NOT NULL
);

插入数据验证

-- 插入两行 sku 为 NULL 的记录
INSERT INTO product (sku, name) VALUES (NULL, '商品A');
INSERT INTO product (sku, name) VALUES (NULL, '商品B');
-- 成功!表中现在有两条 sku 为 NULL 的行

此时查询表可以看到:

id sku name
1 NULL 商品A
2 NULL 商品B

再尝试插入重复的非空值:

INSERT INTO product (sku, name) VALUES ('SKU001', '商品C');
INSERT INTO product (sku, name) VALUES ('SKU001', '商品D');
-- Error: Duplicate entry 'SKU001' for key 'sku'

非空值严格保持唯一性,而多个 NULL 可以和平共存。

复合唯一约束中的 NULL 行为

当唯一约束包含多列时,规则依然适用:只要组合中存在至少一个 NULL ,整行就不会因为其他列重复而被唯一约束拒绝。每一列中的 NULL 都被视为独立未知值。

场景示例

CREATE TABLE team_member (
    id INT PRIMARY KEY,
    team_id INT NOT NULL,
    employee_code VARCHAR(20),
    role VARCHAR(50),
    UNIQUE KEY unique_assignment (team_id, employee_code)
);

插入下列数据:

INSERT INTO team_member VALUES (1, 10, 'E001', '开发');
INSERT INTO team_member VALUES (2, 10, NULL, '测试');
INSERT INTO team_member VALUES (3, 10, NULL, '运维');
-- 全部成功

team_id=10, employee_code=NULL 的组合出现了三次,但并未违反唯一约束。因为引擎在比较 (10, NULL)(10, NULL) 时,由于第二个元素是 NULL,整个组合的比较结果不确定,因此视作不同。

再尝试插入重复的非空组合:

INSERT INTO team_member VALUES (4, 10, 'E001', '设计');
-- Error: Duplicate entry '10-E001' for key 'unique_assignment'

只要涉及 NULL 的列全是非空值,唯一性立即生效。

与其它数据库的差异

并非所有数据库都允许多个 NULL。例如 Microsoft SQL Server 默认只允许在一个唯一约束列中存在一个 NULL(可通过创建过滤唯一索引实现多 NULL)。而 PostgreSQLMySQL 一样,允许多个 NULL。如果你需要跨数据库兼容,务必明确当前系统对 NULL 的处理逻辑。

实战注意事项与最佳实践

1. 业务上需要“唯一”时避免使用 NULL

如果业务要求“手机号要么不填,填了就必须唯一”,直接使用 UNIQUE 没问题。但如果将 NULL 当作“占位符”又要求唯一,就会产生意料之外的多行。此时可考虑:

  • 设置 NOT NULL 约束,并用一个特殊默认值(如空字符串 '')代替 NULL
  • 在应用层或通过触发器进行额外检查。

2. 了解 NULL 的排序与索引

在唯一索引中,键值为 NULL 的条目会出现在索引的最前面或最后面(取决于存储引擎和排序规则),但这不影响唯一性判断。进行 ORDER BY ... ASC 时,MySQL 默认将 NULL 排在最前。

3. 唯一约束与 NOT NULL 配合使用

很多场景下,我们希望列值“不重复且必须有值”,此时应该同时使用 UNIQUENOT NULL。这样既保证唯一性,又彻底绕开 NULL 引发的多义性。

CREATE TABLE order_info (
    order_no VARCHAR(32) NOT NULL UNIQUE,
    ...
);

4. 通过唯一索引显式控制 NULL

如果你希望模拟 SQL Server 那样“只允许一个 NULL”的行为,可以借助函数索引(MySQL 8.0.13+)来达成:

CREATE UNIQUE INDEX idx_single_null ON user ((IFNULL(email, 'NULL_PLACEHOLDER')));

但这种方式会把所有 NULL 都转换成相同字符串,从而只允许一个 NULL。实际使用前需评估副作用。

总结

  • MySQL 唯一约束兼容多个 NULL,因为 NULL 之间互不相等。
  • 这一行为适用于单列和复合唯一约束。
  • 它符合 SQL 标准,但与 SQL Server 等系统存在差异。
  • 设计表结构时,务必区分“未知/未提供”和“有值且唯一”,必要时结合 NOT NULL 来明确业务意图。
  • 理解 NULL 在唯一索引中的处理方式,可以帮助避免数据重复的 Bug,并写出更健壮的数据库模型。