Postgres:为什么选择count(*)花这么长时间

时间:2019-10-18 11:14:57

标签: postgresql

TL; DR

我有一个问题表,查询非常慢。我在上面运行pg_repack来重建表,但是它仍然很慢。不幸的是,pg_repack没有重建表。我必须通过pg_dump转储并重新加载表。

分析显示很多死行。

# analyse verbose payslip;
INFO:  analyzing "public.payslip"
INFO:  "payslip": scanned 30000 of 458337 pages, containing 8732 live rows and 400621 dead rows; 8732 rows in sample, 133407 estimated total rows
ANALYZE

自动真空无法正常工作。本文确定了潜在的问题...

https://www.cybertec-postgresql.com/en/reasons-why-vacuum-wont-remove-dead-rows/

原始线程

我有一张有14万行的表,每周增加约500行。

几周前,我调查了表上的查询,发现所有查询都很慢。例如,select count()花费了6秒。我使用pg_repack重建了表,并假定那是表的结尾。我注意到表格今天又变慢了,选择计数()为3秒。

数据库中有138个表,只有另一个表(具有130万行)花费了超过一秒钟的时间来进行选择计数(*)。

我想知道是否存在损坏,这是否是Postgres中的错误或是否存在调整问题。

信息

这是今天通过psql的计数

# select count(*) from payslip;
 count  
--------
 140327
(1 row)

Time: 3255.772 ms (00:03.256)

这是查询计划

# explain select count(*) from payslip;
                                        QUERY PLAN                                        
------------------------------------------------------------------------------------------
 Aggregate  (cost=142820.48..142820.49 rows=1 width=8)
   ->  Bitmap Heap Scan on payslip  (cost=22543.92..142479.77 rows=136285 width=0)
         ->  Bitmap Index Scan on payslip_idx3  (cost=0.00..22509.84 rows=136285 width=0)
(3 rows)

这是数据模型(被截断)。

                         Table "public.payslip"
          Column          |          Type          | Collation | Nullable |                   Default                    
--------------------------+------------------------+-----------+----------+----------------------------------------------
 taxregno                 | character varying(20)  |           | not null | 
 worksid                  | character varying(8)   |           | not null | 
 cutoffdate               | character(10)          |           | not null | 
 productionid             | integer                |           | not null | 
... 

Ignore 50 columns

Indexes:
    "payslip_pkey" PRIMARY KEY, btree (taxregno, worksid, cutoffdate, productionid)
    "payslip_k1" UNIQUE, btree (taxregno, worksid, cutoffdate, productionid)
    "payslip_idx3" btree (worksid)
    "payslip_idx4" btree (ppsnumber)

Postgres版本当前为11。此数据库库从Postgres 8迁移到当前版本已有10多年的历史。我只是按照各种Ubuntu升级中的说明进行操作。

$ psql -V
psql (PostgreSQL) 11.3 (Ubuntu 11.3-1.pgdg14.04+1)

服务器在具有SSD存储的Linode linux机器上运行。我设置了postgresql.conf页面成本以反映SSD。

#seq_page_cost = 1.0            # measured on an arbitrary scale
random_page_cost = 1.0          # same scale as above

今天

不幸的是,这是生产服务器,我需要在短期内解决性能问题。因此,我现在再次运行pg_repack。

在pg_repack之后

# select count(*) from payslip;
 count  
--------
 140327
(1 row)

Time: 26.216 ms

# explain select count(*) from payslip;
                              QUERY PLAN                              
----------------------------------------------------------------------
 Aggregate  (cost=10974.09..10974.10 rows=1 width=8)
   ->  Seq Scan on payslip  (cost=0.00..10623.27 rows=140327 width=0)
(2 rows)

按照下面的a_horse_with_no_name的要求,这里是进一步的信息。如上所述,这是在重建表后进行的。

# explain (analyze, buffers, timing) select count(*) from payslip;
                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=12850.75..12850.76 rows=1 width=8) (actual time=42.070..42.071 rows=1 loops=1)
   Buffers: shared hit=11022
   ->  Seq Scan on payslip  (cost=0.00..12485.00 rows=146300 width=0) (actual time=0.010..31.669 rows=140327 loops=1)
         Buffers: shared hit=11022
 Planning Time: 0.102 ms
 Execution Time: 42.115 ms
(6 rows)

一周后更新。

这是一个安静的一周。该表增加了250行。 select count(*)从.04秒减至.7秒。该查询从较快的顺序扫描更改为较慢的位图索引扫描。

select count(*) from payslip;
 140572

Time: 643.144 ms

这是细节。

explain (analyze, buffers, timing) select count(*) from payslip;
 Aggregate  (cost=108251.57..108251.58 rows=1 width=8) (actual time=718.015..718.016 rows=1 loops=1)
   Buffers: shared hit=169407
   ->  Bitmap Heap Scan on payslip  (cost=8522.42..107900.14 rows=140572 width=0) (actual time=229.612..707.319 rows=140572 loops=1)
         Heap Blocks: exact=76839 lossy=84802
         Buffers: shared hit=169407
         ->  Bitmap Index Scan on payslip_idx3  (cost=0.00..8487.28 rows=140572 width=0) (actual time=205.228..205.228 rows=2212168 loops=1)
               Buffers: shared hit=7757
 Planning Time: 0.115 ms
 Execution Time: 718.069 ms

两周后更新

距我重建桌子已经两周了。本周表增加了340行。选择计数(*)时间从0.6秒减少到2秒。

select count(*) from payslip;
 count  
--------
 140914
(1 row)

Time: 2077.577 ms (00:02.078)

查询计划没有变化,执行速度慢得多。

explain (analyze, buffers, timing) select count(*) from payslip;
                                                                  QUERY PLAN                                                                  
----------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=138089.00..138089.01 rows=1 width=8) (actual time=2068.305..2068.305 rows=1 loops=1)
   Buffers: shared hit=8 read=324086 written=1
   ->  Bitmap Heap Scan on payslip  (cost=17071.92..137736.72 rows=140914 width=0) (actual time=270.512..2056.755 rows=140914 loops=1)
         Heap Blocks: exact=8198 lossy=301091
         Buffers: shared hit=8 read=324086 written=1
         ->  Bitmap Index Scan on payslip_idx3  (cost=0.00..17036.69 rows=140914 width=0) (actual time=268.801..268.801 rows=4223367 loops=1)
               Buffers: shared read=14794
 Planning Time: 0.164 ms
 Execution Time: 2068.623 ms
(9 rows)

Time: 2069.567 ms (00:02.070)

所选索引(idx3)是重复索引,在140k记录中具有22k唯一值。位图索引扫描表明,本周(插入400次之后)扫描了400万行,上周对同一查询扫描了200万行,这与性能下降相符。

来自索引维护查询的信息(由richyen建议)

 relname | rows_in_bytes | num_rows | number_of_indexes | unique | single_column | multi_column 
---------+---------------+----------+-------------------+--------+---------------+--------------
 payslip | 138 kB        |   140914 |                 4 | Y      |             2 |            2



 schemaname | tablename |  indexname   | num_rows | table_size | index_size | unique | number_of_scans | tuples_read | tuples_fetched 
------------+-----------+--------------+----------+------------+------------+--------+-----------------+-------------+----------------
 public     | payslip   | payslip_k1   |   140914 | 2420 MB    | 244 MB     | Y      |           39720 |  3292501603 |       14295183
 public     | payslip   | payslip_idx4 |   140914 | 2420 MB    | 156 MB     | N      |           43013 |  9529447977 |       34943724
 public     | payslip   | payslip_idx3 |   140914 | 2420 MB    | 116 MB     | N      |           42812 |  3067603558 |       72358879
 public     | payslip   | payslip_pkey |   140914 | 2420 MB    | 244 MB     | Y      |            3540 |   203676311 |        4213496
(4 rows)


  size   |             idx1             |              idx2               |         idx3         | idx4 
---------+------------------------------+---------------------------------+----------------------+------
 488 MB  | payslip_pkey                 | payslip_k1                      |                      | 

在这个阶段,我重新设计了表索引。我将主键设为序列中的整数值,并在所有索引中都包含序列号以使其唯一。

自从重建索引以来,选择计数(*)回到了顺序扫描。我将不得不等待表增长一点,以查看查询是否使数百万行读取。

explain (analyze, buffers, timing) select count(*) from payslip;
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=312850.42..312850.43 rows=1 width=8) (actual time=1348.241..1348.242 rows=1 loops=1)
   Buffers: shared hit=199941 read=111148
   ->  Seq Scan on payslip  (cost=0.00..312498.14 rows=140914 width=0) (actual time=209.227..1336.035 rows=140914 loops=1)
         Buffers: shared hit=199941 read=111148
 Planning Time: 0.069 ms
 Execution Time: 1348.289 ms
(6 rows)

现在索引信息

 schemaname | tablename |  indexname   | num_rows | table_size | index_size | unique | number_of_scans | tuples_read | tuples_fetched 
------------+-----------+--------------+----------+------------+------------+--------+-----------------+-------------+----------------
 public     | payslip   | payslip_pkey |   140914 | 2430 MB    | 91 MB      | Y      |               0 |           0 |              0
 public     | payslip   | payslip_idx2 |   140914 | 2430 MB    | 202 MB     | Y      |               0 |           0 |              0
 public     | payslip   | payslip_idx4 |   140914 | 2430 MB    | 128 MB     | Y      |               0 |           0 |              0
 public     | payslip   | payslip_idx3 |   140914 | 2430 MB    | 128 MB     | N      |               0 |           0 |              0
(4 rows)

问题已解决

我终于找到了解决方案。我的问题是我假设pg_repack按照建议的名称重建了表。没有。桌子完全零散了。

由于某种原因,我不知道为什么使用分散的表,PostgreSQL决定执行顺序扫描而不是索引扫描。

这是我应该看的。

# analyse verbose payslip;
INFO:  analyzing "public.payslip"
INFO:  "payslip": scanned 30000 of 458337 pages, containing 8732 live rows and 400621 dead rows; 8732 rows in sample, 133407 estimated total rows
ANALYZE

使用pg_dump并重新加载表可以很快解决问题。

我进一步研究了这个问题,并找到了这篇很棒的文章。

https://www.cybertec-postgresql.com/en/reasons-why-vacuum-wont-remove-dead-rows/

数据库中有两个准备不好的事务,导致自动真空无法正常工作。

SELECT标识,已准备,所有者,数据库,事务AS xmin

-#FROM pg_prepared_xacts -#ORDER BY age(transaction)DESC;                  gid |准备业主|数据库| xmin
-------------------------------------- + ----------- -------------------- + ------- + ---------- + ---------  _sa_4f7780bb6653ccb70ddaf2143ac7a232 | 2019-08-12 13:00:11.738766 + 01 |凯文|凯文| 1141263  _sa_0db277aebcb444884763fe6245d702fe | 2019-09-19 14:00:11.977378 + 01 |凯文|凯文| 2830229 (2行)

感谢大家的帮助。

1 个答案:

答案 0 :(得分:1)

从上周到本周的变化表明,Future runmodel() async { manager.registerLocalModelSource(FirebaseLocalModelSource( modelName: 'mobilenet_v10', assetFilePath: 'assets/mobilenet_v2.tflite')); var imageBytes = (await rootBundle.load('assets/download.jpg')).buffer; img.Image image = img.decodeJpg(imageBytes.asUint8List()); image = img.copyResize(image, height: 224, width: 224); var results = await interpreter.run(); print(results);} 的许多数据不再在缓存中(请参阅{{1}中paysliphit的变化}}部分。

还要注意,您的read越来越多Buffers:,这意味着您可能将Heap Blocks设置得过低。

这周您可能应该将lossy至少增加到work_mem(因为最新的统计数据表明正在访问约309,000个页面块)。但是,随着表的增长,您可能需要增加它的数量。{work_mem可以基于每个会话进行设置,因此您需要根据表的基于模式的预测进行设置大小(我不喜欢这个主意,但也不建议将25MB设置为任意高,因为全局设置可能会导致内存过度分配)

我对work_mem的内部内容不是很清楚,但是我想知道您是否看到重新打包后的性能提高,因为东西被存储在内存中,并随着时间的流逝而被冲刷掉。

披露:我为EnterpriseDB (EDB)工作