升级到版本 11 后 Postgres 查询产生高磁盘 IO

Posted

技术标签:

【中文标题】升级到版本 11 后 Postgres 查询产生高磁盘 IO【英文标题】:Postgres query making high disk IO after upgrade to version 11 【发布时间】:2021-02-01 12:42:22 【问题描述】:

从 RDS 9.6 版升级到 RDS 11 版后,Postgres 查询开始谈论高读取 IOPS 和 CPU。 数据集与升级前相同。不知道是什么问题。

以下是解释计划:

可能是因为索引损坏了?

Explain (analyze true, verbose true, costs true, buffers true, timing true )
select consumertr0_.ref_id as col_0_0_
from consumer_transactions consumertr0_
where (consumertr0_.remaining_amount is not null)
  and (consumertr0_.expiry_time is not null)
  and consumertr0_.expiry_time>'2020-12-15T00:00:00'
  and consumertr0_.expiry_time<now()
  and consumertr0_.remaining_amount>0
order by consumertr0_.expiry_time asc
limit 20000;

                  
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.57..80391.67 rows=20000 width=24) (actual time=191716.213..192077.229 rows=20000 loops=1)
   Output: ref_id, expiry_time
   Buffers: shared hit=9481343 read=1566736
   I/O Timings: read=97.486
   ->  Index Scan using consumer_transactions_expiry_time_remaining_amount on public.consumer_transactions consumertr0_  (cost=0.57..1109723.40 rows=276081 width=24) (actual time=191716.211..192075.241 rows=20000 loops=1)
         Output: ref_id, expiry_time
         Index Cond: ((consumertr0_.expiry_time > '2020-12-15 00:00:00'::timestamp without time zone) AND (consumertr0_.expiry_time < now()))
         Buffers: shared hit=9481343 read=1566736
         I/O Timings: read=97.486
 Planning Time: 1.525 ms
 Execution Time: 192078.720 ms
(11 rows)

索引定义:

"consumer_transactions_expiry_time_remaining_amount" btree
   (expiry_time, remaining_amount)
WHERE expiry_time IS NOT NULL
  AND remaining_amount IS NOT NULL
  AND remaining_amount > 0::numeric

分析细节:

        relname        |         last_analyze          |       last_autoanalyze      
 consumer_transactions | 2021-01-24 22:00:03.144379+00 | 

之前处理相同数量的记录的速度非常快,IOPS 要求很低。虽然我没有以前版本 9.6 的解释计划。

解决方案: 我用不同的名称创建了相同的索引,它解决了这个问题。一旦确定为什么旧索引在升级后突然变得如此缓慢,我将删除旧索引。

用新索引解释计划:

explain (analyze true, verbose true, costs true, buffers true, timing true )  select consumertr0_.ref_id as col_0_0_ from consumer_transactions consumertr0_ where (consumertr0_.remaining_amount is not null) and (consumertr0_.expiry_time is not null) and consumertr0_.expiry_time>'2019-07-01T00:00:00' and consumertr0_.expiry_time<now() and consumertr0_.remaining_amount>0 order by consumertr0_.expiry_time asc limit 20000;

            
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.57..73592.06 rows=20000 width=24) (actual time=0.048..18.307 rows=20000 loops=1)
   Output: ref_id, expiry_time
   Buffers: shared hit=11140
   ->  Index Scan using consumer_transactions_expiry_time_remaining_amount2 on public.consumer_transactions consumertr0_  (cost=0.57..22273478.26 rows=6053275 width=24) (actual time=0.047..16.119 rows=20000 loops=1)
         Output: ref_id, expiry_time
         Index Cond: ((consumertr0_.expiry_time > '2019-07-01 00:00:00'::timestamp without time zone) AND (consumertr0_.expiry_time < now()))
         Buffers: shared hit=11140
 Planning Time: 1.160 ms
 Execution Time: 19.600 ms
(9 rows)

(END)

旧的解释计划也直接从实际时间 191716.211 开始,而新的解释计划从 0.047 开始。我不明白 191716.211 之前实际花费的时间在哪里。

仅供参考: 索引膨胀详情:

 current_database | schemaname |        tblname        |                       idxname                       |  real_size  | extra_size  |    extra_ratio    | fillfactor | bloat_size  |    bloat_ratio    
| is_na 
------------------+------------+-----------------------+-----------------------------------------------------+-------------+-------------+-------------------+------------+-------------+-------------------
+-------
| f
 proddb            | public     | consumer_transactions | consumer_transactions_expiry_time_remaining_amount  |  4748820480 |  3698360320 |  77.8795563145819 |         90 |  3583516672 |  75.4611947765185 | f
 proddb            | public     | consumer_transactions | consumer_transactions_expiry_time_remaining_amount2 |  1755013120 |   704552960 |  40.1451676896866 |         90 |   589709312 |  33.6014190024973 | f

【问题讨论】:

@Laurenz Albe:请在这里提供帮助。同时我会尝试再次创建相同的索引,以防索引损坏。我知道提取的记录相当高,但记录的数量相同。之前也获取过,我保留了相同的参数更改。 看起来很奇怪,好像表或者索引都臃肿了。 REINDEXVACUUM (FULL) 有帮助吗? 您查询的是热备吗? 您是否有任何长期运行的交易处于未处理状态? @jjanes :我在主(主主)服务器上运行它。没有长时间运行的事务处于打开状态。 【参考方案1】:

旧索引相当臃肿:扫描它必须查看 11048079 个 8kB 块(并从磁盘读取其中 1566736 个)才能找到匹配的行,而新索引只需要查看 11140 个块。

我不确定索引是如何进入这种状态的。

第二个索引列似乎没什么用。

这个查询的完美索引应该是:

CREATE INDEX ON public.consumer_transactions (expiry_time) INCLUDE (ref_id)
WHERE remaining_amount > 0;

如果您VACUUM 表,您将获得快速的仅索引扫描。

【讨论】:

旧索引显示 I/O Timings: read=97.486,即 97 ms,并不多。所以从磁盘中获取应该不是问题。共享命中块也是从磁盘读取的块的 6 倍(共享命中=9481343 读取=1566736),因此共享缓冲区也需要大约 100 毫秒...旧索引扫描的总时间 = 97 毫秒 + 100 毫秒 = 197 毫秒,但这不是反映在解释计划中。为什么旧的解释计划显示实际时间=191716.211..192075.241。还有为什么节点实际开始时间是 191716.211? 191716.211之前的ms去哪儿了? 还有什么让您认为索引或表臃肿?还有什么方法可以检查在总共 100 个索引扫描中... 70% 的索引扫描是从 shared_buffers 发生的,其余的索引扫描是从磁盘发生的....只是要知道索引不适合 RAM 和我们需要对这些做点什么。非常感谢@laurenz Albe 如果你必须趟过 1100 万个索引块才能找到 20000 行,那么这个索引就有点问题了。 谢谢!请您帮助我了解旧索引计划的索引扫描节点的实际时间指标。 索引扫描花了将近 200 秒才找到第一个结果。

以上是关于升级到版本 11 后 Postgres 查询产生高磁盘 IO的主要内容,如果未能解决你的问题,请参考以下文章

升级 JDBC 驱动程序后,Postgres 中到整数的隐式类型转换失败

postgres11.2版本客户端打开column "p.prolang"问题处理

Postgres多版本控制

Heroku Postgres 升级后 Flyway 无法连接到数据库

Postgres 不同的查询计划 Prod/QA

查询(HAVING 子句)在 Mysql 版本 5.7 和 8.0 之间的行为不同