数据库性能之旅(四)关于NULL值

不同数据库对NULL值处理逻辑也不同,总结一下

PostgreSQL

默认情况NULL值是不同的值,也就是未知,未知和未知不相同。PostgreSQL15引入了 NULLS NOT DISTINCT on unique indexes / constraints对NULL的约束更严格了,不同的NULL可以看做是相同的值。

看例子

PostgreSQL 15 引入的 NULLS NOT DISTINCT 选项确实对唯一性约束中 NULL 值的处理方式带来了重要改变。在之前的版本中,NULL 值在唯一性约束中被视为互不相同的"未知"值,因此可以插入多个 NULL 而不违反约束。而新选项允许将多个 NULL 值视为相同的值,从而在唯一性约束中只允许存在一个 NULL。

以下是对这一特性的补充实例说明:

1. 单列唯一约束的对比实例

在 PostgreSQL 14 及更早版本中,即使对列设置了唯一约束,也能插入多个 NULL 值。

sql 复制代码
-- PostgreSQL 14 及更早的行为(默认 NULLS DISTINCT)
CREATE TABLE users_old (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE -- 默认允许多个 NULL
);
INSERT INTO users_old (name) VALUES (NULL);
INSERT INTO users_old (name) VALUES (NULL); -- 成功插入,表中存在两行 name 为 NULL 的记录[5](@ref)

SQL

在 PostgreSQL 15 中,可以通过 NULLS NOT DISTINCT 明确禁止多个 NULL。

sql 复制代码
-- PostgreSQL 15 新行为
CREATE TABLE users_new (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) UNIQUE NULLS NOT DISTINCT -- 将 NULL 视为相同值
);
INSERT INTO users_new (name) VALUES (NULL);
INSERT INTO users_new (name) VALUES (NULL); -- 失败,违反唯一约束[5](@ref)

SQL

2. 多列复合唯一约束的实例

这个特性同样适用于多列组成的复合唯一约束。在旧风格下,只要组合中有一列为 NULL,该行就不会与其他行(即使其他列值完全相同)发生唯一性冲突。

sql 复制代码
-- 传统风格:允许 (val1, val2) 组合中出现多个 val2 为 NULL 的行,只要 val1 相同
CREATE TABLE null_old_style (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    val1 TEXT NOT NULL,
    val2 TEXT NULL,
    CONSTRAINT uq_val1_val2 UNIQUE (val1, val2) -- 默认 NULLS DISTINCT
);
INSERT INTO null_old_style (val1, val2) VALUES ('Hello', NULL);
INSERT INTO null_old_style (val1, val2) VALUES ('Hello', NULL); -- 成功,可插入多行[1](@ref)

SQL

使用 NULLS NOT DISTINCT 后,复合唯一键会将 NULL 视为一个具体的值进行比对。

sql 复制代码
-- 新风格:将 (val1, val2) 组合中的 NULL 视为确定值
CREATE TABLE null_new_style (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    val1 TEXT NOT NULL,
    val2 TEXT NULL,
    CONSTRAINT uq_val1_val2_new UNIQUE NULLS NOT DISTINCT (val1, val2)
);
INSERT INTO null_new_style (val1, val2) VALUES ('Hello', NULL);
INSERT INTO null_new_style (val1, val2) VALUES ('Hello', NULL); -- 失败,违反唯一约束[1](@ref)
INSERT INTO null_new_style (val1, val2) VALUES ('World', NULL); -- 成功,因为 val1 不同[1](@ref)

SQL

3. 在已有表上通过 ALTER TABLE 添加约束的实例

该选项也可以在修改表时使用。

sql 复制代码
-- 创建一个表,不立即添加约束
CREATE TABLE t2 (
    id INT,
    c1 VARCHAR,
    c2 VARCHAR
);


-- 添加一个将 NULL 视为非 distinct 的唯一约束
ALTER TABLE t2 ADD UNIQUE NULLS NOT DISTINCT (c1, c2);


-- 测试插入:第一个 NULL 成功,第二个相同组合的 NULL 失败
INSERT INTO t2 VALUES (1, 'c1', NULL);
INSERT INTO t2 VALUES (2, 'c1', NULL); -- 将报错:违反唯一约束[4](@ref)

SQL

4. 创建唯一索引时的应用实例

除了表约束,该语法也可用于创建唯一索引。

sql 复制代码
CREATE TABLE unique_tbl (i INTEGER);


-- 创建允许 NULL 值重复的唯一索引(传统行为,默认)
CREATE UNIQUE INDEX unique_idx1 ON unique_tbl (i) NULLS DISTINCT;


-- 创建不允许 NULL 值重复的唯一索引
CREATE UNIQUE INDEX unique_idx2 ON unique_tbl (i) NULLS NOT DISTINCT;

SQL

当使用 NULLS NOT DISTINCT 索引后,向表中插入多个 i 列为 NULL 的值将会失败。

5. 需要注意的特殊场景与限制

虽然 NULLS NOT DISTINCT 加强了对 NULL 的唯一性限制,但它并不能直接满足某些更复杂的业务规则。例如,有这样一个需求:当列 b 为 NULL 时,列 a 的值必须唯一;但当 b 不为 NULL 时, a 可以重复。仅使用 UNIQUE NULLS NOT DISTINCT (a, b) 无法实现该逻辑,因为它会将 (a, NULL) 整体视为一个唯一键,允许 (a, 非NULL值) 同时存在,但也会允许 (a, NULL) 与 (a, 另一个非NULL值) 共存,这不符合"b为NULL时a必须唯一"的规则。实现这类复杂逻辑通常需要借助排除约束(Exclusion Constraints)或函数索引等其他机制。

此外PostgreSQL还支持create index xxx on xx where xx is null的语法,方便快速过滤null或者not null值。

Oracle

和PostgreSQL 15之前默认的逻辑相近。

MySQL

和PostgreSQL 15之前默认的逻辑相近。

总结

PostgreSQL 15 的 NULLS NOT DISTINCT 选项为数据库设计提供了更精细的唯一性控制手段,让开发者可以根据业务需求,选择是否将 NULL 视为一个可重复的"未知值",还是一个具有唯一性的确定值。在设计表结构时,应仔细考虑业务语义,以决定是否启用此选项。

相关推荐
学习3人组2 小时前
Conda虚拟环境迁移指南导出依赖库并跨设备重建环境
java·数据库·conda
hgz07102 小时前
MySQL索引数据结构:B+树 vs 哈希索
数据库·sql·mysql
GISERLiu2 小时前
Mapper 怎么能找到实体和数据库
数据库·oracle·mybatis
技术不打烊2 小时前
MySQL锁机制全解:彻底理解行锁、表锁与死锁原理
数据库·mysql
云老大TG:@yunlaoda3602 小时前
华为云国际站代理商如何使用EDCM进行跨账号代维?
大数据·数据库·华为云
飞函安全2 小时前
MongoBleed:MongoDB的秘密漏洞
数据库·安全·mongodb
代码游侠2 小时前
学习笔记——sqlite3 数据库基础
linux·运维·网络·数据库·笔记·学习·sqlite
黄团团2 小时前
Oracle内置DBMS_CRYPTO加密包实现AES对称加密和解密
数据库·oracle
AC赳赳老秦2 小时前
使用PbootCMS制作网站如何免费做好防护
前端·数据库·黑客·网站建设·网站制作·防挂马·网站防黑