具有很多列的唯一索引
Unique index with a lots of columns
我有一个繁忙的 OLTP table,有 30 列和 5000 万行,我想避免其中出现重复。
我应该采取什么方法?
到目前为止我想到了这些:
- 在 30 列上直接唯一索引(听起来像一个巨大的慢索引)
- 某种计算的 30 列总和的唯一索引
- 应用程序端生成的散列放在新的第 31 列中,具有 btree 唯一索引
对于后者,我觉得如果 table 架构发生变化,重新生成该哈希列会很麻烦。
也许还有其他一些我没有想到的方法?
Postgres 14
... 刚刚推出了 records 的内置哈希函数,这比我的自定义函数便宜得多。特别是对于很多列!参见:
- Generate hash id for records in DB
这使得表达式索引比生成的列加索引更具吸引力。所以只是:
CREATE UNIQUE INDEX tbl_row_uni ON tbl (hash_record_extended(tbl.*,0));
这通常也有效:
CREATE UNIQUE INDEX tbl_row_uni ON tbl (hash_record_extended(tbl,0));
但第一个变体更安全。在第二个变体中,如果存在同名列,tbl
将解析为该列。
Postgres 13(原始答案)
我最近在 dba.SE 上提供了该问题的解决方案:
这与您的第三个想法非常接近:
基本上,一个非常有效的服务器端生成的散列被放置在具有 UNIQUE
约束的第 31 列。
CREATE OR REPLACE FUNCTION public.f_tbl_bighash(col1 text, col2 text, ... , col30 text)
RETURNS bigint
LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
'SELECT hashtextextended(textin(record_out((,, ... ,))), 0)';
ALTER TABLE tbl
ADD COLUMN tbl_bighash bigint NOT NULL GENERATED ALWAYS AS (public.f_tbl_bighash(col1, col2, ... , col30)) STORED -- append column in last position
, ADD CONSTRAINT tbl_bighash_uni UNIQUE (tbl_bighash);
它的优点:无需更改任何其他内容即可高效运行。 (除非您在没有目标列表或类似内容的情况下使用 SELECT *
或 INSERT INTO
。)
它也适用于 NULL
个值(将它们视为相等)。
如果任何列类型具有非 immutable 文本表示,请小心。 (喜欢 timestamptz
。)解决方案使用所有 text
列进行测试。
如果 table 架构更改 ,首先删除 UNIQUE
约束,重新创建函数并重新创建生成的列 - 理想情况下使用单个 ALTER TABLE
语句,所以你不会重写 table 两次。
或者,使用基于public.f_tbl_bighash()
的UNIQUE
表达式索引。同样的效果。好处:没有额外的 table 列。缺点:在计算上有点贵。
我有一个繁忙的 OLTP table,有 30 列和 5000 万行,我想避免其中出现重复。
我应该采取什么方法?
到目前为止我想到了这些:
- 在 30 列上直接唯一索引(听起来像一个巨大的慢索引)
- 某种计算的 30 列总和的唯一索引
- 应用程序端生成的散列放在新的第 31 列中,具有 btree 唯一索引
对于后者,我觉得如果 table 架构发生变化,重新生成该哈希列会很麻烦。
也许还有其他一些我没有想到的方法?
Postgres 14
... 刚刚推出了 records 的内置哈希函数,这比我的自定义函数便宜得多。特别是对于很多列!参见:
- Generate hash id for records in DB
这使得表达式索引比生成的列加索引更具吸引力。所以只是:
CREATE UNIQUE INDEX tbl_row_uni ON tbl (hash_record_extended(tbl.*,0));
这通常也有效:
CREATE UNIQUE INDEX tbl_row_uni ON tbl (hash_record_extended(tbl,0));
但第一个变体更安全。在第二个变体中,如果存在同名列,tbl
将解析为该列。
Postgres 13(原始答案)
我最近在 dba.SE 上提供了该问题的解决方案:
这与您的第三个想法非常接近:
基本上,一个非常有效的服务器端生成的散列被放置在具有 UNIQUE
约束的第 31 列。
CREATE OR REPLACE FUNCTION public.f_tbl_bighash(col1 text, col2 text, ... , col30 text)
RETURNS bigint
LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
'SELECT hashtextextended(textin(record_out((,, ... ,))), 0)';
ALTER TABLE tbl
ADD COLUMN tbl_bighash bigint NOT NULL GENERATED ALWAYS AS (public.f_tbl_bighash(col1, col2, ... , col30)) STORED -- append column in last position
, ADD CONSTRAINT tbl_bighash_uni UNIQUE (tbl_bighash);
它的优点:无需更改任何其他内容即可高效运行。 (除非您在没有目标列表或类似内容的情况下使用 SELECT *
或 INSERT INTO
。)
它也适用于 NULL
个值(将它们视为相等)。
如果任何列类型具有非 immutable 文本表示,请小心。 (喜欢 timestamptz
。)解决方案使用所有 text
列进行测试。
如果 table 架构更改 ,首先删除 UNIQUE
约束,重新创建函数并重新创建生成的列 - 理想情况下使用单个 ALTER TABLE
语句,所以你不会重写 table 两次。
或者,使用基于public.f_tbl_bighash()
的UNIQUE
表达式索引。同样的效果。好处:没有额外的 table 列。缺点:在计算上有点贵。