Postgres - 创建 EXCLUDE 约束非常慢
Postgres - very slow to create EXCLUDE constraint
我们有一个 table foo
,它的架构如下所示
hi=# \d foo
Table "public.foo"
Column | Type | Modifiers
------------+--------------------------+------------------------
id | uuid | not null
bar_id | uuid | not null
hi | character varying(128) | not null
yo | character varying(4000) |
updated_at | timestamp with time zone | not null default now()
created_at | timestamp with time zone | not null default now()
Indexes:
"foo$pk" PRIMARY KEY, btree (id)
"foo$uk" UNIQUE CONSTRAINT, btree (bar_id, hi, yo)
Foreign-key constraints:
"foo$bar$fk" FOREIGN KEY (bar_id) REFERENCES bar(id)
我们里面有大约100M条记录,如你所见,这个table有一个UNIQUE
约束,我们想做的是用[=14替换它=] 由于业务原因的限制。所以我们想要做出的改变如下所示
ALTER TABLE foo ADD CONSTRAINT "foo$one$uk"
EXCLUDE ( bar_id WITH =, hi WITH =, yo WITH =) WHERE (hi = 'Tom') DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE foo ADD CONSTRAINT "foo$two$uk"
EXCLUDE ( bar_id WITH =, hi WITH =) WHERE (hi = 'Lisa') DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE foo DROP CONSTRAINT IF EXISTS "foo$uk";
证据表明,运行 在具有(m3.large + 300GB 通用 SSD)的 AWS RDS 实例上执行这 3 条语句需要大约 12 个小时才能完成。但我们也注意到 运行 第一个语句几乎一直在消耗,第二个很快(几分钟内),第三个立即返回。所以我想知道幕后发生了什么,为什么会这样?
无论何时添加约束,都应检查现有数据以确保不存在现有约束违规。
排除约束描述为,see 5.3.6:
ensure that if any two rows are compared on the specified columns or expressions using the specified operators.
因此,根据 hi = 'Tom'
的行数,您可能对 1 亿行执行了 O(n2) 操作。是的,这需要一段时间。
另请注意:
Adding an exclusion constraint will automatically create an index of the type specified in the constraint declaration.
这有一些开销,但没有比较每对现有行那么多。
至于第二个约束,我不确定,但有 2 种可能性为什么它约束 运行 更快。
要么行数明显减少WHERE hi = 'Lisa'
,要么引擎可以利用先前约束已被检查的事实中的信息,以便更有效地检查新约束。
显然第三个变化,删除约束,不需要检查任何东西。
旁注
您可以选择在创建约束检查时将其禁用。 (我不知道 PostgreSQL 是否支持这个。)
- 这允许您忽略现有的约束违规,但确保继续检查约束。
- 这会产生显着加快约束创建速度的副作用。
- 当然这也意味着约束还没有被“验证”。这意味着引擎不能“信任”约束的完整性,否则可能会获得任何性能优势。
我们有一个 table foo
,它的架构如下所示
hi=# \d foo
Table "public.foo"
Column | Type | Modifiers
------------+--------------------------+------------------------
id | uuid | not null
bar_id | uuid | not null
hi | character varying(128) | not null
yo | character varying(4000) |
updated_at | timestamp with time zone | not null default now()
created_at | timestamp with time zone | not null default now()
Indexes:
"foo$pk" PRIMARY KEY, btree (id)
"foo$uk" UNIQUE CONSTRAINT, btree (bar_id, hi, yo)
Foreign-key constraints:
"foo$bar$fk" FOREIGN KEY (bar_id) REFERENCES bar(id)
我们里面有大约100M条记录,如你所见,这个table有一个UNIQUE
约束,我们想做的是用[=14替换它=] 由于业务原因的限制。所以我们想要做出的改变如下所示
ALTER TABLE foo ADD CONSTRAINT "foo$one$uk"
EXCLUDE ( bar_id WITH =, hi WITH =, yo WITH =) WHERE (hi = 'Tom') DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE foo ADD CONSTRAINT "foo$two$uk"
EXCLUDE ( bar_id WITH =, hi WITH =) WHERE (hi = 'Lisa') DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE foo DROP CONSTRAINT IF EXISTS "foo$uk";
证据表明,运行 在具有(m3.large + 300GB 通用 SSD)的 AWS RDS 实例上执行这 3 条语句需要大约 12 个小时才能完成。但我们也注意到 运行 第一个语句几乎一直在消耗,第二个很快(几分钟内),第三个立即返回。所以我想知道幕后发生了什么,为什么会这样?
无论何时添加约束,都应检查现有数据以确保不存在现有约束违规。
排除约束描述为,see 5.3.6:
ensure that if any two rows are compared on the specified columns or expressions using the specified operators.
因此,根据 hi = 'Tom'
的行数,您可能对 1 亿行执行了 O(n2) 操作。是的,这需要一段时间。
另请注意:
Adding an exclusion constraint will automatically create an index of the type specified in the constraint declaration.
这有一些开销,但没有比较每对现有行那么多。
至于第二个约束,我不确定,但有 2 种可能性为什么它约束 运行 更快。
要么行数明显减少WHERE hi = 'Lisa'
,要么引擎可以利用先前约束已被检查的事实中的信息,以便更有效地检查新约束。
显然第三个变化,删除约束,不需要检查任何东西。
旁注
您可以选择在创建约束检查时将其禁用。 (我不知道 PostgreSQL 是否支持这个。)
- 这允许您忽略现有的约束违规,但确保继续检查约束。
- 这会产生显着加快约束创建速度的副作用。
- 当然这也意味着约束还没有被“验证”。这意味着引擎不能“信任”约束的完整性,否则可能会获得任何性能优势。