在 Postgresql 中索引外键
Posted
技术标签:
【中文标题】在 Postgresql 中索引外键【英文标题】:Indexing Foreign Keys in Postgresql 【发布时间】:2020-03-22 19:51:48 【问题描述】:像许多 Postgres n00bs
一样,我们有很多带有未索引的外键约束的表。在某些情况下,这不应该对性能造成很大影响 - 但这需要进一步分析。
我已阅读以下文章:https://www.cybertec-postgresql.com/en/index-your-foreign-key/
并使用以下查询查找所有没有索引的外键:
SELECT c.conrelid::regclass AS "table",
/* list of key column names in order */
string_agg(a.attname, ',' ORDER BY x.n) AS columns,
pg_catalog.pg_size_pretty(
pg_catalog.pg_relation_size(c.conrelid)
) AS size,
c.conname AS constraint,
c.confrelid::regclass AS referenced_table
FROM pg_catalog.pg_constraint c
/* enumerated key column numbers per foreign key */
CROSS JOIN LATERAL
unnest(c.conkey) WITH ORDINALITY AS x(attnum, n)
/* name for each key column */
JOIN pg_catalog.pg_attribute a
ON a.attnum = x.attnum
AND a.attrelid = c.conrelid
WHERE NOT EXISTS
/* is there a matching index for the constraint? */
(SELECT 1 FROM pg_catalog.pg_index i
WHERE i.indrelid = c.conrelid
/* the first index columns must be the same as the
key columns, but order doesn't matter */
AND (i.indkey::smallint[])[0:cardinality(c.conkey)-1]
@> c.conkey::int[])
AND c.contype = 'f'
GROUP BY c.conrelid, c.conname, c.confrelid
ORDER BY pg_catalog.pg_relation_size(c.conrelid) DESC;
这向我展示了具有复合唯一约束的表,只有唯一索引中的“一”列:
\d topics_items;
-----------------+---------+--------------+---------------+------------------------------
topics_items_id | integer | | not null | generated always as identity
topic_id | integer | | not null |
item_id | integer | | not null |
Index:
"topics_items_pkey" PRIMARY KEY, btree (topics_items_id)
"topic_id_item_id_unique" UNIQUE CONSTRAINT, btree (topic_id, item_id)
Foreign Keys:
"topics_items_item_id_fkey" FOREIGN KEY (item_id) REFERENCES items(item_id) ON DELETE CASCADE
"topics_items_topic_id_fkey" FOREIGN KEY (topic_id) REFERENCES topics(topic_id) ON DELETE CASCADE
在这种情况下,检查查询仅找到 item_id
而不是 topic_id
作为未索引字段。
公平地说,这只是所使用查询的问题,我必须分别索引两个字段(topic_id 和 item_id) - 或者是否涉及一些黑色巫术,只有 item_id
需要索引?
【问题讨论】:
【参考方案1】:tl;dr您需要在item_id
上添加索引。 Postgres 索引的“黑魔法”在11. Indexes 中有介绍。
您在(topic_id, item_id)
上有一个复合索引,列顺序很重要。 Postgres 可以使用它来索引topic_id
上的查询、topic_id
和item_id
上的查询,但不能(或效率较低)单独item_id
。
来自11.3. Multicolumn Indexes...
多列 B 树索引可用于涉及索引列的任何子集的查询条件,但当前导(最左侧)列存在约束时,索引效率最高。
-- indexed
select *
from topics_items
where topic_id = ?
-- also indexed
select *
from topics_items
where topic_id = ?
and item_id = ?
-- probably not indexed
select *
from topics_items
where item_id = ?
这是因为像(topic_id, item_id)
这样的复合索引首先存储主题 ID,然后是也具有该主题 ID 的项目 ID。为了在该索引中有效地查找项目 ID,Postgres 必须首先使用主题 ID 缩小搜索范围。
Postgres 可以在认为值得努力的情况下反转索引。如果可能的主题 ID 数量较少,而可能的索引 ID 数量较多,则会在每个主题 ID 中搜索索引 ID。
例如,假设您有 10 个可能的主题 ID 和 1000 个可能的项目 ID,并且您的索引为 (topic_id, index_id)
。这就像有 10 个清晰标记的主题 ID 存储桶,每个存储桶内部都有 1000 个清晰标记的项目 ID 存储桶。要访问项目 ID 存储桶,它必须查看每个主题 ID 存储桶的内部。要在 where item_id = 23
上使用此索引,Postgres 必须在 10 个主题 ID 桶中的每一个桶中搜索项目 ID 为 23 的所有桶。
但如果您有 1000 个可能的主题 ID 和 10 个可能的项目 ID,Postgres 将不得不搜索 1000 个主题 ID 存储桶。它很可能会改为进行全表扫描。在这种情况下,您需要反转索引并将其设为 (item_id, topic_id)
。
这在很大程度上取决于拥有良好的表统计信息,这意味着确保 autovacuum 正常工作。
因此,如果一列的可变性远小于另一列,则可以为两列使用单个索引。
Postgres can also use mulitple indexes if it thinks it will make the query run faster。例如,如果您在topic_id
上有一个索引,在item_id
上有一个索引,它可以使用这两个索引并组合结果。例如where topic_id = 23 or item_id = 42
可以使用topic_id 索引搜索主题ID 23,使用item_id 索引搜索项目ID 42,然后合并结果。
这通常比使用复合 (topic_id, item_id)
索引要慢。它也可能比使用单个索引慢,所以如果 Postgres 决定不使用多个索引,请不要感到惊讶。
通常,对于 b-tree 索引,当您有两列时,您有三种可能的组合。
a + b 一个 b而且你需要两个索引。
(a, b) -- a 和 a + b (b) -- b(a, b)
涵盖对 a 和 a + b 的搜索。 (b)
涵盖搜索 b
。
当你有三列时,你有七种可能的组合。
a + b + c a + b a + c 一个 b + c b c但你只需要三个索引。
(a, b, c) -- a, a + b, a + b + c (b, c) -- b, b + c (c, a) -- c, c + a但是,您实际上可能希望避免在三列上创建索引。它通常较慢。你真正想要的是这个。
(a, b) (b, c) (c, a)应谨慎使用多列索引。在大多数情况下,单个列上的索引就足够了,并且可以节省空间和时间。除非表格的使用非常风格化,否则超过三列的索引不太可能有帮助。
从索引读取比从表读取慢。您希望您的索引减少必须读取的行数,但您不希望 Postgres 必须进行任何不必要的索引扫描。
右侧列的约束...在索引中检查,因此它们可以正确保存对表的访问,但不会减少必须扫描的索引部分。例如,给定 (a, b, c) 上的索引和查询条件 WHERE a = 5 AND b >= 42 AND c = 77 的索引条目将被跳过,但仍必须扫描它们。
【讨论】:
【参考方案2】:使用(topic_id, item_id)
上的索引可以有效地找到具有特定topic_id
的行,这就是我的查询认为外键被覆盖的原因。
索引按topic_id
排序,在所有具有相同topic_id
的条目中,它按item_id
排序。这使得它可以单独用于搜索 topic_id
。
【讨论】:
以上是关于在 Postgresql 中索引外键的主要内容,如果未能解决你的问题,请参考以下文章