Postgres会自动将索引放在外键和主键上吗?我该怎么说?是否有一个命令可以返回表中的所有索引?
答案 0 :(得分:331)
PostgreSQL会自动在主键和唯一约束上创建索引,但不会在外键关系的引用端创建索引。
当Pg创建隐式索引时,它会发出NOTICE
级别的消息,您可以在psql
和/或系统日志中看到这些消息,这样您就可以看到它何时发生。在表的\d
输出中也可以看到自动创建的索引。
documentation on unique indexes说:
PostgreSQL自动为每个唯一约束和主键约束创建索引以强制实现唯一性。因此,没有必要为主键列显式创建索引。
以及constraints上的文档说:
由于引用表中的行的DELETE或者a的UPDATE 引用列将需要扫描引用表 与旧值匹配的行,通常是一个好主意索引 引用列。因为这并不总是需要,而且有 关于如何索引,声明外键的许多选择 约束不会自动在引用上创建索引 列。
因此,如果需要,您必须自己在外键上创建索引。
请注意,如果您使用主外键(例如2 FK作为M-to-N表中的PK),您将拥有PK的索引,并且可能不需要创建任何额外索引。< / p>
虽然在引用端外键列上创建索引(或包括)通常是个好主意,但这不是必需的。您添加的每个索引都会略微降低DML操作的速度,因此您需要为每个INSERT
,UPDATE
或DELETE
支付性能费用。如果指数很少使用,则可能不值得拥有。
答案 1 :(得分:32)
如果要从程序中列出架构中所有表的索引,则所有信息都在目录中:
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]
可以很方便地找出如何查询目录。
答案 2 :(得分:20)
答案 3 :(得分:17)
此查询将列出外键上缺少的索引,original source。
-- 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;
答案 4 :(得分:9)
我喜欢文章Cool performance features of EclipseLink 2.5
中对此的解释索引外键
第一个功能是自动索引外键。大多数人错误地认为数据库索引 外键默认情况下。好吧,他们没有。主键是自动的 索引,但外键不是。这意味着任何基于的查询 外键将进行全表扫描。这是任何 OneToMany , ManyToMany 或 ElementCollection 关系,以及很多 OneToOne 关系和关于任何涉及连接或关系的关系的大多数查询 对象比较。这可能是一个主要的执行问题,你应该这样做 始终索引您的外键字段。
答案 5 :(得分:7)
对于PRIMARY KEY
,将使用以下消息创建索引:
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "index" for table "table"
对于FOREIGN KEY
,如果referenc ed 表中没有索引,则不会创建约束。
不需要参考参考 ing 表的索引(尽管需要),因此不会隐式创建。
答案 6 :(得分:2)
此功能基于Laurenz Albe在https://www.cybertec-postgresql.com/en/index-your-foreign-key/的工作,列出了所有缺少索引的外键。显示了表的大小,对于小表,扫描性能可能优于索引1。
--
-- 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;
以这种方式进行测试
select * from oftool_fkey_missing_indexes();
您会看到这样的列表。
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|
答案 7 :(得分:1)
这里是一个批处理脚本,它生成 SQL 以使用@sergeyB 的 SQL 为缺少的外键索引创建索引。
#!/bin/bash
read -r -d '' SQL <<EOM
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_to_string(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 relname as table_name,
cols_list
FROM fk_index_check
JOIN parent_table_stats USING (fkoid)
JOIN fk_table_stats USING (fkoid)
ORDER BY issue_sort, table_mb DESC, table_name;
EOM
DB_NAME="dbname"
DB_USER="dbuser"
DB_PASSWORD="dbpass"
DB_HOSTNAME="hostname"
DB_PORT=5432
export PGPASSWORD="$DB_PASSWORD"
psql -h $DB_HOSTNAME -p $DB_PORT -U $DB_USER -d $DB_NAME -t -A -F"," -c "$SQL" | while read -r line; do
IFS=','
parts=($line)
unset IFS
tableName=${parts[0]}
colsList=${parts[1]}
indexName="${tableName}_${colsList//:/_}_index"
printf -- "\n--Index: %s\nDROP INDEX IF EXISTS %s;\n
CREATE INDEX %s\n\t\tON %s USING btree\n\t(%s);
" "$indexName" "$indexName" "$indexName" "$tableName" "$colsList"
done