Postgres 更新不使用主键索引

Posted

技术标签:

【中文标题】Postgres 更新不使用主键索引【英文标题】:Postgres update not using primary key index 【发布时间】:2018-08-16 16:08:30 【问题描述】:

我们的新 Postgres 应用在测试中遇到问题。当我们使用主键运行更新时,它看起来对主键而不是索引使用序列扫描。该表太大,无法保证使用全扫描,所以我不确定它为什么选择这种方法。

pdns=# \d+ pdns.meta_data
Table "schema.table"
  Column   |          Type           | 
 id        | integer                 | 
 block_id  | integer                 | 
 feed_name | character varying(100)  | 
 metadata  | character varying(1000) | 
Indexes:
    "table_pkey" PRIMARY KEY, btree (id)

pdns=# select * from pg_stat_all_tables where schemaname not like 'pg%';
-[ RECORD 1 ]-------+------------------------------
relid               | 16403
schemaname          | schema
relname             | table
seq_scan            | 26720      <------ Times sequence scan is used
seq_tup_read        | 87233270740
idx_scan            | 1          <------ Times Index scan is used
idx_tup_fetch       | 0
n_tup_ins           | 26714
n_tup_upd           | 26714
n_tup_del           | 0
n_tup_hot_upd       | 2407
n_live_tup          | 3278722           <------ Actual rows
n_dead_tup          | 2122
n_mod_since_analyze | 2196
last_vacuum         | 2018-08-16 14:51:12.559508+00
last_autovacuum     | 2018-08-16 15:37:55.617413+00
last_analyze        | 2018-08-16 15:10:38.768696+00
last_autoanalyze    | 2018-08-16 15:38:55.205594+00
vacuum_count        | 1
autovacuum_count    | 4
analyze_count       | 2
autoanalyze_count   | 8

您可以看到它正在执行大量的 seq_scans 并且没有 idex 扫描(那是我手动执行的)。查询是使用绑定变量运行的。应用调用的查询是:

postgres=# SELECT   min_time, max_time,  calls,  query FROM pg_stat_statements WHERE query like'%TABLE%';
 min_time  |  max_time  | calls |                                     query
-----------+------------+-------+--------------------------------------------------------------------------------
           |            |       | RETURNING *
 604.26355 | 2447.10553 | 31591 | update SCHEMA.TABLE set block_id=$1 where id=$2
(1 rows)    
postgres=#

但是当我手动运行它时,我得到了索引扫描:

pdns=# prepare stmt(int) as update SCHEMA.TABLE set block_id=$1 where id=$2;
PREPARE
pdns=# explain execute stmt(1,2);
QUERY PLAN | Update on table (cost=0.43..8.45 rows=1 width=102)
QUERY PLAN |   ->  Index Scan using table_pkey on table(cost=0.43..8.45 rows=1 width=102)
QUERY PLAN |         Index Cond: (id = 2)

这太奇怪了。任何想法都将不胜感激。

非常感谢 H

【问题讨论】:

我猜你的应用程序(或它使用的 ORM)正在做一些阴暗的事情。尝试启用auto_explain 并在生产环境中调试您缓慢的顺序扫描查询。 谢谢,是的,我刚刚做到了:Query Text: update SCHEMA.TABLE set block_id=$1 where id=$2 Update on table (cost=0.00..110291.14 rows=17624 width=102) &gt; Seq Scan on table (cost=0.00..110291.14 rows=17624 width=102) Filter: ((id)::numeric = '3525375'::numeric) 所以是的,在浏览应用程序时,它肯定是使用顺序扫描。由于sql是自包含的,是否有外部影响使其选择顺序扫描? 我相信它使用的是顺序扫描,因为它认为它在那里提取了更多的行 (17624),因此 seq 扫描更加优化。但由于搜索列是主键,它只有一个条目,所以我不确定这是否是统计数据错误的情况。我已经收集了这张表的统计数据。 真的很奇怪!尝试:1)在桌子上运行 VACUUM ANALYZE; 2) 摆弄random_page_cost 和cpu_index_tuple_cost,也许它们已经偏离了(或者你的应用程序在启动时将这两个设置为自定义值——只是在这里猜测); 3) 重新创建违规索引; 4) SET enable_seqscan = off(但仅作为调试措施) 另外,主键索引是否设置为唯一? 【参考方案1】:

我们最终找到了答案,它实际上在 auto_explain 日志中:

Query Text: update SCHEMA.TABLE set block_id=$1 where id=$2 Update on table 
(cost=0.00..110291.14 rows=17624 width=102) > Seq Scan on table (cost=0.00..110291.14 
rows=17624 width=102) Filter: ((id)::numeric = '3525375'::numeric)

关键是

((id)::numeric = '3525375'::numeric)

这表明查询正在搜索整数从数字的转换。

当列类型为整数时,应用程序实际上将数据作为数字放入。因此,插入语句正在执行隐式数据类型更改,从而导致索引不被使用。 通过将应用程序更改为使用整数而不是数字数据类型来修复它 呸。希望这对其他人有所帮助。 谢谢你的建议丽娜

【讨论】:

不错,没注意到::numeric

以上是关于Postgres 更新不使用主键索引的主要内容,如果未能解决你的问题,请参考以下文章

外键和主键的 Postgres 和索引

更新不使用索引 postgres

Postgres ON CONFLICT 缺少我声明支持唯一索引的主键冲突

Postgres:更新所有表的主键序列

在 postgres 中更新具有大量删除的表的主键序列

在Postgres中的物化视图上创建主键