外键和主键的Postgres和索引

mai*_*rgs 308 sql database postgresql foreign-keys

Postgres会自动将索引放在外键和主键上吗?我该怎么说?是否有一个命令可以返回表上的所有索引?

Phi*_*ipp 366

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

当Pg创建一个隐式索引时,它将发出一个NOTICE你可以在psql系统日志和/或系统日志中看到的级别消息,这样你就可以看到它何时发生.自动创建的索引也在\d表的输出中可见.

有关唯一索引文档说:

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

关于约束的文档说:

由于引用表中的行的DELETE或引用列的UPDATE将需要扫描引用表以查找与旧值匹配的行,因此通常最好对引用列建立索引.因为并不总是需要这个,并且有很多关于如何索引的选择,所以外键约束的声明不会自动在引用列上创建索引.

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

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

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

  • 我希望这个编辑没问题; 我添加了相关文档的链接,这个引用使得FK关系的引用方面没有产生隐式索引,显示了如何在psql中查看索引,为了清晰起见改写第1个标准,并添加了请注意,索引不是免费的,因此添加它们并不总是正确的. (25认同)
  • @Gili 这是一个单独的 dba.stackexchange.com 问题的主题。 (4认同)

dla*_*and 33

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

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
Run Code Online (Sandbox Code Playgroud)

如果您想进一步深入研究(例如列和排序),您需要查看pg_catalog.pg_index.使用psql -E [dbname]派上用场搞清楚如何查询目录.

  • +1因为使用pg_catalog和psql -E确实非常有用 (4认同)

Ser*_*eyB 24

此查询将列出外键,原始源上的缺失索引.

-- 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;
Run Code Online (Sandbox Code Playgroud)

  • 似乎不起作用.当我知道我的列没有引用域表的索引时,返回0行. (6认同)
  • @juanitogan观察`where`子句:除其他外,它只考虑表格大小超过9 MB. (4认同)

Mil*_*dev 20

是 - 对于主键,否 - 对于外键(在文档中更多).

\d <table_name>
Run Code Online (Sandbox Code Playgroud)

in "psql"显示包含其所有索引的表的描述.

  • 作为参考,\ di还将列出数据库中的所有索引. (11认同)

Nab*_*abi 12

我喜欢在EclipseLink 2.5的Cool performance features文章中解释这一点

索引外键

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

  • 如果我们应该**总是**索引我们的外键字段,为什么数据库引擎不能这样做呢?在我看来,除此之外还有更多的东西. (5认同)
  • @Bobort 由于添加索引会导致所有插入、更新和删除的性能下降,并且在这种情况下,许多外键确实可以加起来。这就是为什么我认为这种行为是可选的 - 开发人员应该在这件事上做出有意识的选择。也可能存在外键用于强制执行数据完整性但不经常查询或根本不查询的情况 - 在这种情况下,索引的性能损失将毫无意义 (5认同)
  • 还有一些复杂的情况,因为复合索引是从左到右应用的:即,注释表上[user_id,article_id]上的复合索引将有效地覆盖用户查询所有注释(例如,在网站上显示汇总注释)和获取所有该用户对特定文章的评论。在这种情况下,在user_id上添加单独的索引实际上会浪费磁盘空间和插入/更新/删除操作的CPU时间。 (3认同)
  • 啊哈!那么建议很差!我们不应该总是索引我们的外键。正如@ Dr.Strangelove指出的那样,实际上有时候我们不想索引它们!非常感谢您,博士! (2认同)

Qua*_*noi 7

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

NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "index" for table "table" 
Run Code Online (Sandbox Code Playgroud)

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

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


cot*_*ros 5

此函数基于 Laurenz Albe 在https://www.cybertec-postgresql.com/en/index-your-foreign-key/上的工作,列出所有缺少索引的外键。显示了表的大小,对于小表,扫描性能可能优于索引表。

--
-- function:    fkeys_missing_indexes
-- purpose:     list all foreing keys in the database without and index in the source table.
-- author:      Laurenz Albe
-- see:         https://www.cybertec-postgresql.com/en/index-your-foreign-key/
--
create or replace function oftool_fkey_missing_indexes () 
returns table (
  src_table     regclass,
  fk_columns    varchar,
  table_size    varchar,
  fk_constraint name,
  dst_table     regclass
)
as $$
select
  -- source table having ta foreign key declaration
  tc.conrelid::regclass as src_table,
  
  -- ordered list of foreign key columns
  string_agg(ta.attname, ',' order by tx.n) as fk_columns,
  
  -- source table size
  pg_catalog.pg_size_pretty (
    pg_catalog.pg_relation_size(tc.conrelid)
  ) as table_size,
  
  -- name of the foreign key constraint
  tc.conname as fk_constraint,
  
  -- name of the target or destination table
  tc.confrelid::regclass as dst_table
  
from pg_catalog.pg_constraint tc

-- enumerated key column numbers per foreign key
cross join lateral unnest(tc.conkey) with ordinality as tx(attnum, n)

-- name for each key column
join pg_catalog.pg_attribute ta on ta.attnum = tx.attnum and ta.attrelid = tc.conrelid

where not exists (
  -- is there ta matching index for the constraint?
  select 1 from pg_catalog.pg_index i
  where 
    i.indrelid = tc.conrelid and 
    -- the first index columns must be the same as the key columns, but order doesn't matter
    (i.indkey::smallint[])[0:cardinality(tc.conkey)-1] @> tc.conkey) and 
    tc.contype = 'f'
  group by 
    tc.conrelid, 
    tc.conname, 
    tc.confrelid
  order by 
    pg_catalog.pg_relation_size(tc.conrelid) desc;
$$ language sql;
Run Code Online (Sandbox Code Playgroud)

这样测试一下

select * from oftool_fkey_missing_indexes();
Run Code Online (Sandbox Code Playgroud)

你会看到一个这样的列表。

fk_columns            |table_size|fk_constraint                     |dst_table        |
----------------------|----------|----------------------------------|-----------------|
id_group              |0 bytes   |fk_customer__group                |im_group         |
id_product            |0 bytes   |fk_cart_item__product             |im_store_product |
id_tax                |0 bytes   |fk_order_tax_resume__tax          |im_tax           |
id_product            |0 bytes   |fk_order_item__product            |im_store_product |
id_tax                |0 bytes   |fk_invoice_tax_resume__tax        |im_tax           |
id_product            |0 bytes   |fk_invoice_item__product          |im_store_product |
id_article,locale_code|0 bytes   |im_article_comment_id_article_fkey|im_article_locale|
Run Code Online (Sandbox Code Playgroud)

  • 非常有用,@coterobarros。下一步是让它生成 DDL 脚本,以防您想要创建它们...... (2认同)