You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL含ORDER BY子句的查询优化问题求助

优化PostgreSQL慢查询:ORDER BY导致的性能问题

有一个PostgreSQL查询执行耗时1分钟以上,排查发现是ORDER BY aggregatet0_.id ASC子句导致的。移除该子句后,查询1秒内就能完成。疑惑点:结果集仅3410条记录,排序3000条数据不该这么慢,是不是查询在遍历全表?该表有70多万条记录。

查询语句

SELECT aggregatet0_.id                     AS id,
       aggregatet0_.pan                    AS col_1_0,
       aggregatet0_.account_number         AS col_2_0,
       aggregatet0_.local_date             AS col_3_0,
       aggregatet0_.local_time             AS col_4_0,
       aggregatet0_.stan                   AS col_5_0,
       aggregatet0_.rrn                    AS col_6_0,
       aggregatet0_.other_ref              AS col_7_0,
       aggregatet0_.sequence_number        AS col_8_0,
       aggregatet0_.amount                 AS col_9_0,
       aggregatet0_.transaction_type       AS col_10_0,
       aggregatet0_.cur_code               AS col_11_0,
       aggregatet0_.data                   AS col_12_0,
       aggregatet0_.posting_date           AS col_13_0,
       aggregatet0_.transmission_date_time AS col_14_0,
       aggregatet0_.response_code          AS col_15_0,
       aggregatet0_.issuer                 AS col_16_0,
       aggregatet0_.recon_status           AS col_17_0,
       aggregatet0_.matched_in             AS col_18_0,
       aggregatet0_.in_review              AS col_19_0,
       aggregatet0_.notes                  AS col_20_0,
       aggregatet0_.recon_account          AS col_21_0
FROM   recon_core.aggregate_transaction aggregatet0_
       LEFT OUTER JOIN recon_core.recon_account_type reconaccou1_
                    ON ( reconaccou1_.id = aggregatet0_.recon_account_type )
WHERE  ( Coalesce('MATCHED', NULL, '') = ''
          OR aggregatet0_.recon_status IN ( 'MATCHED' ) )
       AND ( Coalesce('RAAST', 'Digital', 'POS', 'Branch', 'ATM', '1LINK', NULL,
             '') =
             ''
              OR reconaccou1_.account_type IN (
                 'RAAST', 'Digital', 'POS', 'Branch',
                 'ATM', '1LINK' ) )
       AND ( Coalesce(NULL, NULL, '') = ''
              OR aggregatet0_.destination IN ( NULL ) )
       AND ( ( NULL IS NULL )
             AND ( NULL IS NULL )
              OR Extract(hour FROM aggregatet0_.capture_date_time) BETWEEN NULL
                 AND
                 NULL )
       AND ( Coalesce(NULL, NULL, '') = ''
              OR aggregatet0_.recon_account IN ( NULL ) )
       AND ( 17001 IN ( 17001 )
              OR aggregatet0_.recon_account_id IN ( 17001 ) )
       AND aggregatet0_.posting_date = '2023-06-05'
       AND ( Coalesce('DEPOSIT', 'FUNDS-TRANSFER-OWN', 'CASH-WITHDRAWAL',
                   'CASH-WITHDRAWAL-REVERSAL', 'PURCHASE',
             'FUNDS-TRANSFER-SWIFT',
                   'BALANCE-INQUIRY', 'REFUND', 'CASH-WITHDRAWAL-RETRACT',
             'BILL-PAYMENT',
                   'FUNDS-TRANSFER-LOCAL', 'FUNDS-TRANSFER-INTERBANK', NULL, '')
             = ''
              OR aggregatet0_.transaction_type IN (
                     'DEPOSIT', 'FUNDS-TRANSFER-OWN',
                     'CASH-WITHDRAWAL', 'CASH-WITHDRAWAL-REVERSAL',
                                                    'PURCHASE',
                     'FUNDS-TRANSFER-SWIFT',
                     'BALANCE-INQUIRY', 'REFUND',
                                                    'CASH-WITHDRAWAL-RETRACT',
                     'BILL-PAYMENT'
                     ,
                     'FUNDS-TRANSFER-LOCAL', 'FUNDS-TRANSFER-INTERBANK' ) )
ORDER  BY aggregatet0_.id ASC
LIMIT  15;  

移除ORDER BY aggregatet0_.id ASC后,查询执行正常。

现有索引(均为B树索引)

  • id(主键)
  • transaction_type
  • posting_date

更新:查询计划

Limit  (cost=0.42..2151.51 rows=15 width=2218) (actual time=56156.995..56157.052 rows=15 loops=1)
  ->  Nested Loop  (cost=0.42..200051.50 rows=1395 width=2218) (actual time=56156.993..56157.048 rows=15 loops=1)
        Join Filter: (aggregatet0_.recon_account_type = reconaccou1_.id)
        Rows Removed by Join Filter: 45
        ->  Index Scan using pk__aggregat__3213e83f059b2b7d on aggregate_transaction aggregatet0_  (cost=0.42..199876.96 rows=2092 width=2222) (actual time=56156.569..56156.612 rows=15 loops=1)
              Filter: (((recon_status)::text = 'MATCHED'::text) AND (posting_date = '2023-06-05'::date) AND ((transaction_type)::text = ANY ('{DEPOSIT,FUNDS-TRANSFER-OWN,CASH-WITHDRAWAL,CASH-WITHDRAWAL-REVERSAL,PURCHASE,FUNDS-TRANSFER-SWIFT,BALANCE-INQUIRY,REFUND,CASH-WITHDRAWAL-RETRACT,BILL-PAYMENT,FUNDS-TRANSFER-LOCAL,FUNDS-TRANSFER-INTERBANK}'::text[])))
              Rows Removed by Filter: 752365
        ->  Materialize  (cost=0.00..1.19 rows=6 width=4) (actual time=0.027..0.028 rows=4 loops=15)
              ->  Seq Scan on recon_account_type reconaccou1_  (cost=0.00..1.16 rows=6 width=4) (actual time=0.401..0.404 rows=4 loops=1)
                    Filter: ((account_type)::text = ANY ('{RAAST,Digital,POS,Branch,ATM,1LINK}'::text[]))
                    Rows Removed by Filter: 5
Planning Time: 0.330 ms
Execution Time: 56157.120 ms

问题分析

从查询计划可以明确:慢的根源不是排序3000条数据,而是PostgreSQL选择了从主键索引从头开始扫描,逐一过滤符合条件的记录,总共扫描了752365条(全表数据)才找到15条符合要求的记录。

因为加了ORDER BY id ASC + LIMIT 15,优化器认为从主键索引按顺序扫描,找到15条符合条件的记录就停止,比先筛选所有符合条件的记录再排序取前15更高效,但实际情况是符合条件的记录在全表中占比极低,导致全表扫描耗时极长。

优化方案

1. 创建针对性复合索引

创建覆盖WHERE过滤条件的复合索引,同时包含排序字段,让优化器可以快速筛选出符合条件的记录,再排序取前15:

CREATE INDEX idx_agg_posting_date_recon_status_type ON recon_core.aggregate_transaction 
(posting_date, recon_status, transaction_type) 
INCLUDE (id);
  • posting_date是等值查询,放在索引最前列,快速缩小扫描范围
  • 后续的recon_status和transaction_type都是过滤条件,进一步筛选数据
  • INCLUDE (id)避免排序时回表查询主键,提升排序效率

2. 简化冗余WHERE条件

原查询中的很多Coalesce判断都是冗余的(比如Coalesce('MATCHED', NULL, '') = ''结果为false,实际生效的是OR后的分支),简化后让优化器更容易判断执行路径:

WHERE aggregatet0_.recon_status = 'MATCHED'
  AND reconaccou1_.account_type IN ('RAAST', 'Digital', 'POS', 'Branch', 'ATM', '1LINK')
  AND aggregatet0_.recon_account_id IN (17001)
  AND aggregatet0_.posting_date = '2023-06-05'
  AND aggregatet0_.transaction_type IN ('DEPOSIT', 'FUNDS-TRANSFER-OWN', 'CASH-WITHDRAWAL', 'CASH-WITHDRAWAL-REVERSAL', 'PURCHASE', 'FUNDS-TRANSFER-SWIFT', 'BALANCE-INQUIRY', 'REFUND', 'CASH-WITHDRAWAL-RETRACT', 'BILL-PAYMENT', 'FUNDS-TRANSFER-LOCAL', 'FUNDS-TRANSFER-INTERBANK')

注意:原查询的LEFT JOIN因为WHERE中用到了reconaccou1_.account_type,实际会被优化为INNER JOIN,可以明确写出INNER JOIN避免歧义。

3. 验证优化效果

执行EXPLAIN ANALYZE查看新的查询计划,应该会看到优化器使用新创建的复合索引进行扫描,而不是全表扫描主键索引,筛选出符合条件的记录后排序取前15的耗时会大幅降低。

内容的提问来源于stack exchange,提问作者Faheem Sultan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 20:36:59