MySQL UNIQUE索引中NULL和““的行为为什么不一样详解

 更新时间:2026年08月17日 10:21:02   作者:小丁学Java  
最近遇到个问题,MySQL中UNIQUE索引为何允许多个NULL却拒绝重复空字符串,查找相关资料终于解决了,这篇文章主要介绍了MySQL UNIQUE索引中NULL和““的行为为什么不一样的相关资料,需要的朋友可以参考下

前言

你可能听说过这样一句话:

“在 MySQL 中,UNIQUE 索引允许存在多个 NULL 值,但只允许一个空字符串。”

这句话的结论是对的,但表述不够准确——它只说了"是什么",没说"为什么",容易让人误以为这是 MySQL 对 NULL 的特殊照顾,或者是某个需要死记硬背的规则。

实际上,理解了 NULL 的本质语义,这个行为就是理所当然的结果。

本文从根源出发,彻底讲清楚这个问题。

一、先搞清楚 NULL 和 “” 到底是什么

在深入 UNIQUE 索引之前,必须先把这两个概念分清楚,很多人在这里就已经混淆了。

NULL —— 未知 / 不存在

NULL 不是一个值,它表示的是**“这个字段的值未知"或"这个字段不存在”**。

patient_user 表中:
email = NULL   →  这个患者没有填邮箱,我们不知道他有没有邮箱
email = ''     →  这个患者填了邮箱,填的内容是空的
email = 'a@b.com' →  这个患者的邮箱是 a@b.com

这三种状态在业务语义上是完全不同的三件事,不能混为一谈。

“” —— 空字符串,一个确定的值

空字符串 "" 是一个真实存在的值,只是内容为空。它和 "hello""123" 一样,是一个确定的字符串值,只不过长度为 0。

💡 生活比喻

  • NULL 就像一张没有填写姓名栏的表格,我们不知道这个人叫什么。
  • "" 就像一张姓名栏被故意留空的表格,这个人知道有姓名栏,但选择什么都不填。
  • "张三" 就是正常填写了姓名。

二、UNIQUE 索引的判断逻辑

UNIQUE 索引的核心工作是:判断新插入的值是否已经存在于索引中

判断的依据只有一个:值是否相等(=)

这里就引出了 NULL 行为不同的根本原因。

NULL 的比较规则:NULL != NULL

这是 SQL 标准(ISO/IEC 9075)中明确规定的:

任何涉及 NULL 的比较运算,结果都不是 TRUE 或 FALSE,而是 UNKNOWN。

SELECT NULL = NULL;    -- 结果:NULL(不是 TRUE!)
SELECT NULL != NULL;   -- 结果:NULL
SELECT NULL = '';      -- 结果:NULL
SELECT '' = '';        -- 结果:1(TRUE)

你可以直接在 MySQL 里执行上面的语句验证。

为什么 NULL = NULL 不等于 TRUE?

因为 NULL 代表"未知",两个"未知"之间根本无法比较:

问:这个患者的邮箱 == 那个患者的邮箱吗?
答:不知道,因为两个都不知道是什么。

就像你问"这个盒子里的东西和那个盒子里的东西一样吗",两个盒子都没打开,无从回答,只能说"未知"。

对 UNIQUE 索引的影响

UNIQUE 索引在插入数据时,会用 = 来判断新值是否和已有值冲突:

新值 = 已有值  →  TRUE   →  冲突,拒绝插入
新值 = 已有值  →  FALSE  →  不冲突,允许插入
新值 = 已有值  →  UNKNOWN →  ???

当结果是 UNKNOWN 时,MySQL 遵循 SQL 标准的处理方式:无法确认冲突,视为不冲突,允许插入。

这就是为什么多个 NULL 可以共存的根本原因——不是 MySQL 网开一面,而是 NULL 根本无法被判断为"相等",UNIQUE 索引自然无从拦截。

而空字符串 "" 是一个确定的值,"" = "" 的结果是 TRUE,第二个空字符串插入时,UNIQUE 索引能明确判断出冲突,直接报错。

三、动手验证

建表

CREATE TABLE test_unique (
    id   INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(100),
    UNIQUE KEY uk_email (email)
);

测试 NULL

INSERT INTO test_unique (email) VALUES (NULL);
-- ✅ 成功,id=1

INSERT INTO test_unique (email) VALUES (NULL);
-- ✅ 成功,id=2(第二个 NULL 也能插入!)

INSERT INTO test_unique (email) VALUES (NULL);
-- ✅ 成功,id=3(第三个也没问题)

测试空字符串

INSERT INTO test_unique (email) VALUES ('');
-- ✅ 成功,id=4

INSERT INTO test_unique (email) VALUES ('');
-- ❌ 报错!
-- ERROR 1062 (23000): Duplicate entry '' for key 'uk_email'

查看当前数据

SELECT * FROM test_unique;

-- 结果:
-- id | email
-- 1  | NULL
-- 2  | NULL
-- 3  | NULL
-- 4  | (空字符串)

三个 NULL 和平共处,但第二个空字符串直接被拦截,结论得证。

四、这会带来哪些实际问题?

问题一:password 字段用空字符串代替"未设置密码"的隐患

看这张表的 password 字段设计:

`password` varchar(100) NOT NULL DEFAULT '' 
COMMENT '密码,用空字符串代表未设置密码'

如果 password 字段加了 UNIQUE 约束(虽然一般不会),那么第二个"未设置密码"的用户就会插入失败。这个场景虽然极端,但说明了一个原则:

⚠️ 用空字符串代表"无值"状态,是有风险的设计。NULL 才是表达"无值"的语义正确选择。

问题二:email 字段加 UNIQUE 约束的陷阱

回到本文开头的场景,患者表的 email 字段:

`email` varchar(100) DEFAULT NULL COMMENT '电子邮箱',
UNIQUE KEY `uk_email` (`email`)

由于 email 允许 NULL,多个患者不填邮箱没问题,可以共存。

但一旦有患者填了邮箱,就不能和任何人重复。在医疗场景里,一家人可能共用一个邮箱,这会导致第二个家庭成员注册时报唯一冲突错误,体验极差。

结论:对于"仅作为联系方式"而非"登录凭证"的邮箱字段,不应该加 UNIQUE 约束。

✅ 需要 UNIQUE:邮箱是登录账号(SaaS系统、企业后台)
❌ 不需要 UNIQUE:邮箱只是联系方式(医疗患者表、电商收货信息)

问题三:查询 NULL 不能用 =

既然 NULL 的比较结果是 UNKNOWN,那查询 NULL 值也不能用普通的等号:

-- ❌ 查不到任何结果,因为 NULL = NULL 结果是 UNKNOWN,不是 TRUE
SELECT * FROM test_unique WHERE email = NULL;

-- ✅ 正确写法:用 IS NULL
SELECT * FROM test_unique WHERE email IS NULL;

-- ✅ 查非 NULL 值:用 IS NOT NULL
SELECT * FROM test_unique WHERE email IS NOT NULL;

这是一个非常常见的 Bug 来源,很多初学者在 WHERE 条件里写 = NULL 然后纳闷为什么查不到数据。

五、三值逻辑(Three-Valued Logic)

上面提到 NULL 的比较结果不是 TRUE/FALSE,而是 UNKNOWN,这背后是 SQL 标准引入的三值逻辑

逻辑值含义
TRUE
FALSE
UNKNOWN未知(涉及 NULL 的比较)

WHERE 条件只有结果为 TRUE 时才返回该行,结果为 FALSE 或 UNKNOWN 都不返回:

-- 假设 email 为 NULL
WHERE email = 'a@b.com'   -- UNKNOWN → 不返回
WHERE email != 'a@b.com'  -- UNKNOWN → 不返回(!很多人没想到这个)
WHERE email IS NULL        -- TRUE    → 返回
WHERE email IS NOT NULL    -- FALSE   → 不返回

💡 反直觉的地方WHERE email != 'a@b.com' 也查不到 email 为 NULL 的行!因为结果是 UNKNOWN,不是 TRUE。

六、NULL 的正确使用姿势

适合用 NULL 的场景

-- 可选填的字段,不填就是真的不知道/不存在
`email`    VARCHAR(100) DEFAULT NULL  -- 未填邮箱
`birthday` DATE         DEFAULT NULL  -- 未填生日
`avatar`   VARCHAR(255) DEFAULT NULL  -- 未上传头像

不适合用 NULL 的场景

-- 有业务含义的状态,不应该用 NULL 来表示
-- ❌ 错误:用 NULL 表示"未设置密码"
`password` VARCHAR(100) DEFAULT NULL

-- ✅ 正确:用空字符串表示"未设置密码"(如本表的设计)
`password` VARCHAR(100) NOT NULL DEFAULT ''

-- ❌ 错误:数值类统计字段用 NULL
`available_points` INT DEFAULT NULL

-- ✅ 正确:数值类字段给 0 作为默认值
`available_points` INT NOT NULL DEFAULT '0'

NULL 在聚合函数中的行为

-- NULL 会被 COUNT、SUM、AVG 等聚合函数自动忽略
SELECT COUNT(email) FROM patient_user;    -- 只统计非 NULL 的行
SELECT COUNT(*) FROM patient_user;        -- 统计所有行(包括 email 为 NULL 的)
SELECT AVG(weight) FROM patient_user;     -- NULL 的 weight 不参与计算

⚠️ 陷阱COUNT(字段名)COUNT(*) 的结果可能不同,当字段有 NULL 值时,前者会少统计。

七、总结

对比维度NULL“” 空字符串
语义未知 / 不存在确定的空值
存储不占用字段存储空间(用标志位)占用存储空间(长度为0的字符串)
UNIQUE 索引✅ 允许多个共存(NULL != NULL)❌ 只允许一个(“” == “”)
查询方式IS NULL / IS NOT NULL= '' / != ''
参与聚合计算❌ 被自动忽略✅ 参与计算
适用场景可选字段、真正不存在的值有明确"空"语义的必填字段

一句话总结

🎯 NULL 不是值,是状态。UNIQUE 索引用 = 判断冲突,而 NULL = NULL 的结果是 UNKNOWN,无法判断冲突,所以多个 NULL 能共存。空字符串是确定的值,"" = "" 是 TRUE,冲突立刻被拦截。搞清楚这一点,所有行为都是理所当然的。

到此这篇关于MySQL UNIQUE索引中NULL和““的行为为什么不一样的文章就介绍到这了,更多相关MySQL UNIQUE索引NULL内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Mysql数据库百万级数据测试索引效果

    Mysql数据库百万级数据测试索引效果

    这篇文章主要为大家介绍了Mysql数据库百万数据测试索引效果,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • MySQL数据过滤与计算字段实战指南

    MySQL数据过滤与计算字段实战指南

    本文介绍了MySQL数据过滤与计算字段的实战技术,包括多条件组合、通配符过滤、正则表达式搜索和计算字段的使用,感兴趣的朋友跟随小编一起看看吧
    2025-11-11
  • Mysql实现定时清空一张表的旧数据并保留几条数据(推荐)

    Mysql实现定时清空一张表的旧数据并保留几条数据(推荐)

    这篇文章主要介绍了Mysql实现定时清空一张表的旧数据并保留几条数据,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • 详解Mysql中保证缓存与数据库的双写一致性

    详解Mysql中保证缓存与数据库的双写一致性

    在一些高并发场景下,为了提升系统的性能,我们通常会将数据存储在 Redis 缓存中,并通过 Redis 缓存来提高系统的读取速度,这篇文章主要介绍了详解Mysql中保证缓存与数据库的双写一致性,需要的朋友可以参考下
    2024-03-03
  • 一文深入探讨MySQL是如何解决幻读问题

    一文深入探讨MySQL是如何解决幻读问题

    SQL标准中定义了4种隔离级别,分别是读未提交、读已提交、可重复读以及序列化。不同的隔离级别下,本文将重点探讨下MySQL是如何解决幻读问题的,需要的朋友可以跟着小编一起来探讨一下
    2023-07-07
  • Windows Server 2003下修改MySQL 5.5数据库data目录

    Windows Server 2003下修改MySQL 5.5数据库data目录

    这篇文章主要介绍了Windows Server 2003下修改MySQL 5.5数据库data目录,需要的朋友可以参考下
    2016-04-04
  • 5个MySQL GUI工具推荐,帮助你进行数据库管理

    5个MySQL GUI工具推荐,帮助你进行数据库管理

    这篇文章主要介绍了5个MySQL GUI工具推荐,帮助大家更好的进行MySQL数据库管理,感兴趣的朋友可以了解下
    2020-08-08
  • 浅谈Mysql tinyint(1)与tinyint(4)的区别

    浅谈Mysql tinyint(1)与tinyint(4)的区别

    本文主要介绍了浅谈Mysql tinyint(1)与tinyint(4)的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • MySQL常见错误以及解决方法

    MySQL常见错误以及解决方法

    该文章详细介绍了在使用MySQL数据库时可能遇到的常见错误及其解决方法,涵盖了权限问题、配置错误、文件问题、网络问题等多种情况,文章内容全面,实用性强,适合数据库管理员和开发人员参考
    2024-12-12
  • 在centos7下安装和部署java8和mysql

    在centos7下安装和部署java8和mysql

    一般学习java和部署项目都是在本地部署,但是生产环境一般都是在linux环境下,部署和安装环境都是在控制台下进行操作的,没有windows的可视化的操作界面,对与linux的命令掌握和操作对小白来说都是一个个挑战,记录下自己的安装配置过程
    2017-04-04

最新评论