Postgres和外键和主键索引


Answers:


405

PostgreSQL自动在主键和唯一约束上创建索引,但不在外键关系的引用端创建索引。

当Pg创建隐式索引时,它将发出NOTICE-level消息,您可以在psql和/或系统日志中看到该消息,因此可以看到它何时发生。自动创建的索引在\d表的输出中也可见。

关于唯一索引文档说:

PostgreSQL自动为每个唯一约束和主键约束创建一个索引,以强制执行唯一性。因此,没有必要为主键列显式创建索引。

有关约束的文档说:

由于从被引用表中删除一行或对被引用列进行UPDATE都需要对引用表进行扫描以查找与旧值匹配的行,因此对索引列进行索引通常是一个好主意。因为这并不总是需要的,并且在如何建立索引方面有很多选择,所以外键约束的声明不会自动在引用列上创建索引。

因此,如果需要,您必须自己在外键上创建索引。

请注意,如果您使用主外键,例如2对FK作为M-N表中的PK,则您将在PK上有一个索引,并且可能不需要创建任何额外的索引。

虽然在引用侧外键列上(或包括)创建索引通常是一个好主意,但这不是必需的。每个索引添加减慢DML操作略有下降,所以你在每交纳履约成本INSERTUPDATEDELETE。如果很少使用该索引,则可能不值得使用。


26
我希望此编辑可以;我已经添加了相关文档的链接,用引号可以明确表明FK关系的引用方不会生成隐式索引,显示了如何在psql中查看索引,为清楚起见改写了第一个参数,并添加了一个请注意,索引不是免费的,因此添加索引并不总是正确的。
Craig Ringer

1
@CraigRinger,您如何确定索引的收益是否超过其成本?我是否在添加索引之前/之后分析单元测试,并检查总体性能提升?或者,还有更好的方法?
吉利2014年

2
@Gili这是一个单独的dba.stackexchange.com问题的主题。
Craig Ringer 2014年

34

如果要从程序中列出模式中所有表的索引,则所有信息都在目录中:

select
     n.nspname  as "Schema"
    ,t.relname  as "Table"
    ,c.relname  as "Index"
from
          pg_catalog.pg_class c
     join pg_catalog.pg_namespace n on n.oid        = c.relnamespace
     join pg_catalog.pg_index i     on i.indexrelid = c.oid
     join pg_catalog.pg_class t     on i.indrelid   = t.oid
where
        c.relkind = 'i'
    and n.nspname not in ('pg_catalog', 'pg_toast')
    and pg_catalog.pg_table_is_visible(c.oid)
order by
     n.nspname
    ,t.relname
    ,c.relname

如果要进一步研究(例如列和顺序),则需要查看pg_catalog.pg_index。使用psql -E [dbname]可以很方便地弄清楚如何查询目录。


4
+1,因为使用pg_catalog和psql -E确实非常有用
Ghislain Leveque

作为参考,\di还将列出数据库中的所有索引。(注释从其他答案中复制,也适用于此)
Risadinha

33

该查询将列出外键原始来源)上缺少的索引

编辑:请注意,它不会检查小表(少于9 MB)和其他一些情况。见最终WHERE声明。

-- check for FKs where there is no matching index
-- on the referencing side
-- or a bad index

WITH fk_actions ( code, action ) AS (
    VALUES ( 'a', 'error' ),
        ( 'r', 'restrict' ),
        ( 'c', 'cascade' ),
        ( 'n', 'set null' ),
        ( 'd', 'set default' )
),
fk_list AS (
    SELECT pg_constraint.oid as fkoid, conrelid, confrelid as parentid,
        conname, relname, nspname,
        fk_actions_update.action as update_action,
        fk_actions_delete.action as delete_action,
        conkey as key_cols
    FROM pg_constraint
        JOIN pg_class ON conrelid = pg_class.oid
        JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
        JOIN fk_actions AS fk_actions_update ON confupdtype = fk_actions_update.code
        JOIN fk_actions AS fk_actions_delete ON confdeltype = fk_actions_delete.code
    WHERE contype = 'f'
),
fk_attributes AS (
    SELECT fkoid, conrelid, attname, attnum
    FROM fk_list
        JOIN pg_attribute
            ON conrelid = attrelid
            AND attnum = ANY( key_cols )
    ORDER BY fkoid, attnum
),
fk_cols_list AS (
    SELECT fkoid, array_agg(attname) as cols_list
    FROM fk_attributes
    GROUP BY fkoid
),
index_list AS (
    SELECT indexrelid as indexid,
        pg_class.relname as indexname,
        indrelid,
        indkey,
        indpred is not null as has_predicate,
        pg_get_indexdef(indexrelid) as indexdef
    FROM pg_index
        JOIN pg_class ON indexrelid = pg_class.oid
    WHERE indisvalid
),
fk_index_match AS (
    SELECT fk_list.*,
        indexid,
        indexname,
        indkey::int[] as indexatts,
        has_predicate,
        indexdef,
        array_length(key_cols, 1) as fk_colcount,
        array_length(indkey,1) as index_colcount,
        round(pg_relation_size(conrelid)/(1024^2)::numeric) as table_mb,
        cols_list
    FROM fk_list
        JOIN fk_cols_list USING (fkoid)
        LEFT OUTER JOIN index_list
            ON conrelid = indrelid
            AND (indkey::int2[])[0:(array_length(key_cols,1) -1)] @> key_cols

),
fk_perfect_match AS (
    SELECT fkoid
    FROM fk_index_match
    WHERE (index_colcount - 1) <= fk_colcount
        AND NOT has_predicate
        AND indexdef LIKE '%USING btree%'
),
fk_index_check AS (
    SELECT 'no index' as issue, *, 1 as issue_sort
    FROM fk_index_match
    WHERE indexid IS NULL
    UNION ALL
    SELECT 'questionable index' as issue, *, 2
    FROM fk_index_match
    WHERE indexid IS NOT NULL
        AND fkoid NOT IN (
            SELECT fkoid
            FROM fk_perfect_match)
),
parent_table_stats AS (
    SELECT fkoid, tabstats.relname as parent_name,
        (n_tup_ins + n_tup_upd + n_tup_del + n_tup_hot_upd) as parent_writes,
        round(pg_relation_size(parentid)/(1024^2)::numeric) as parent_mb
    FROM pg_stat_user_tables AS tabstats
        JOIN fk_list
            ON relid = parentid
),
fk_table_stats AS (
    SELECT fkoid,
        (n_tup_ins + n_tup_upd + n_tup_del + n_tup_hot_upd) as writes,
        seq_scan as table_scans
    FROM pg_stat_user_tables AS tabstats
        JOIN fk_list
            ON relid = conrelid
)
SELECT nspname as schema_name,
    relname as table_name,
    conname as fk_name,
    issue,
    table_mb,
    writes,
    table_scans,
    parent_name,
    parent_mb,
    parent_writes,
    cols_list,
    indexdef
FROM fk_index_check
    JOIN parent_table_stats USING (fkoid)
    JOIN fk_table_stats USING (fkoid)
WHERE table_mb > 9
    AND ( writes > 1000
        OR parent_writes > 1000
        OR parent_mb > 10 )
ORDER BY issue_sort, table_mb DESC, table_name, fk_name;

7
似乎不起作用。当我知道自己的列中没有引用域表的索引时,将返回0行。
juanitogan '16

6
@juanitogan请注意以下where条款:除其他事项外,它仅考虑大小超过9 MB的表。
Matthias

@Matthias-知道了。谢谢。是的,我显然没有花时间阅读代码。这还不够关键。OP可能已经提到了局限性。也许我会再检查一次。
juanitogan '17

@SergeyB似乎对具有主键约束的引用列给出假肯定,因此会自动具有索引,但查询仍会对其进行标记。
Debasish Mitra


14

我喜欢EclipseLink 2.5的出色性能特性一文中的解释

索引外键

第一个功能是自动索引外键。大多数人错误地认为数据库默认情况下会索引外键。好吧,他们没有。主键是自动索引的,但外键不是。这意味着任何基于外键的查询都将进行全表扫描。这是任何OneToManyManyToManyElementCollection关系,以及许多 OneToOne 关系,以及对涉及联接或对象比较的任何关系的大多数查询。这可能是一个主要的执行问题,您应该始终索引外键字段。


5
如果我们应该总是索引外键字段,那么数据库引擎为什么不这样做呢?在我看来,这不仅仅是眼神。
Bobort

3
@Bobort由于添加索引会导致所有插入,更新和删除操作的性能下降,因此在这种情况下,实际上可能会添加许多外键。我猜这就是为什么选择这种行为的原因-开发人员应该对此事做出有意识的选择。在某些情况下,可能还会使用外键来增强数据完整性,但并不经常查询或根本不查询外键–在这种情况下,索引的性能损失将是
毫无用处的

3
还有一些复杂的情况,因为复合索引是从左到右应用的:即注释表上[user_id,article_id]上的复合索引将有效地覆盖用户查询所有注释(例如,在网站上显示汇总注释)和获取所有该用户对特定文章的评论。在这种情况下,在user_id上添加单独的索引实际上会浪费磁盘空间和插入/更新/删除操作的CPU时间。
Dr.Strangelove

2
啊哈!那么建议很差!我们不应该总是索引我们的外键。正如@ Dr.Strangelove指出的那样,实际上有时候我们不想索引它们!非常感谢您,博士!
Bobort

为什么默认情况下不对它们进行索引?是否有重要的用例使其成为必要?
亚当·阿罗德

7

对于PRIMARY KEY,将使用以下消息创建索引:

NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "index" for table "table" 

对于FOREIGN KEY,约束也不会,如果有对和借鉴没有索引创建表。

上和借鉴索引ING表不是必需的(尽管需要的话),并且因此将不被隐式地创建。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.