业务人员反馈 GaussDB 里出现了一条慢 SQL,直接拖慢了功能验证流程。我们随后对这条 SQL 展开了排查和优化,过程比较典型,分享出来供有类似问题的读者参考。
性能分析
SQL 文本如下:
SELECT calc.header_id AS "dtlaccountId", acc.accentity_id AS "accentity", acc.accbook_id AS "accbook", acc.asset_id AS "assetId", acc.busi_date AS "busiDate" , calc.direction AS "direction", calc.bln_depr AS "isdepr" FROM FIEFA.fa_ledger_b_calc calc, FIEFA.fa_ledger_h acc WHERE calc.header_id = acc.id AND acc.accentity_id = '1873583679575949319' AND acc.accbook_id IN ('1892143719336378386') AND acc.asset_id IN ('1933638798553907250') AND acc.src_bill_type_id = '2314079661183488EFA' AND acc.ytenant_id = 'qgb2au5x' AND acc.busi_date < '2026-07-01' AND acc.busi_date IS NOT NULL ORDER BY acc.busi_date DESC
这条 SQL 的执行计划如下:

问题诊断(瓶颈定位)
总耗时 1756.830 ms,其中 1552 ms 几乎全部耗费在 fa_ledger_h 的 Index Scan 上——这是典型的"索引前导列选对了,但选择性严重不足,被 Filter 大量过滤"的场景。
Index Scan using idx_fa_ledger_h_optimize on fa_ledger_h acc
(actual time=1552.152..1756.173 rows=1 loops=1)
Index Cond: ((ytenant_id)::text = 'qgb2au5x'::text AND (accentity_id)::text = '1873583679575949319'::text)
Filter: (busi_date IS NOT NULL) AND ((busi_date)::text < '2026-07-01'::text) AND ((accbook_id)::text = '1892143719336378386'::text) AND ((asset_id)::text = '1933638798553907250'::text) AND ((src_bill_type_id)::text = '2314079661183488EFA'::text)
Rows Removed by Filter: 480231 ← 关键证据
注意 Rows Removed by Filter: 480231 这一行,索引扫描返回了 48 万行候选记录,最终只有 1 行满足条件。剩下的 480,231 行全部被 Filter 丢弃,大量的无效扫描拖垮了整条查询。
处理结果
收集 fa_ledger_h 和 fa_ledger_b_calc 两张表的统计信息之后,新的执行计划如下:

优化效果:1756 ms → 0.614 ms,性能提升约 2860 倍。
新执行计划的关键部分解读:
Sort (cost=10.33..10.34 rows=1) (actual time=0.268..0.269 rows=2 loops=1)
Sort Key: acc.busi_date DESC
Sort Method: quicksort Memory: 25kB
-> Nested Loop (cost=0.00..10.32 rows=1) (actual time=0.205..0.224 rows=2 loops=1)
-> Index Scan using i_fa_ledger_h_asset_id on fa_ledger_h acc
(cost=0.00..8.03 rows=1) (actual time=0.174..0.185 rows=1 loops=1)
Index Cond: (asset_id = '1933638798553907250')
Filter: (busi_date IS NOT NULL) AND (busi_date < '2026-07-01')
AND (accentity_id = '1873583679575949319')
AND (accbook_id = '1892143719336378386')
Rows Removed by Filter: 31 ← 关键指标
-> Index Scan using i_fa_ledger_b_calc_header_id on calc
Index Cond: (header_id = acc.id)
Total runtime: 0.614 ms
新旧计划对比
| 维度 |
旧计划 |
新计划 |
| 驱动索引 |
idx_fa_ledger_h_optimize (ytenant_id, accentity_id) |
i_fa_ledger_h_asset_id |
| Rows Removed by Filter |
480,231 |
31 |
| 驱动表耗时 |
1552 ms |
0.185 ms |
| 总耗时 |
1756.830 ms |
0.614 ms |
根因分析
其实根源不在索引本身,而在统计信息。旧统计信息下,优化器对 (ytenant_id, accentity_id) 组合的行数估算严重偏低,甚至认为只会返回 1 行。基于这个错误的估算,优化器判断走 idx_fa_ledger_h_optimize 最划算。结果索引实际扫出了 48 万行,只有 1 行匹配,这就是典型的"规划器估算偏差 → 选错索引 → 实际执行雪崩"。
执行 ANALYZE 刷新统计信息之后,优化器重新评估:asset_id 的基数远高于 (ytenant_id, accentity_id) 组合。更准确地说,asset_id 这一列的数据分布更加稀疏,每张资产在头表里基本只有一条记录,选择性反而最强。于是优化器迅速切换到 i_fa_ledger_h_asset_id,过滤后仅剩 31 行,再经 Nested Loop 关联,整体耗时直接降到亚毫秒级别。
这次调优也再次说明:统计信息过时是慢 SQL 的常见诱因之一。遇到优化器"固执"地选错索引时,不妨先确认统计信息是否过期,很多场景下会收到立竿见影的效果。
类似的技术实战案例与讨论,欢迎到 云栈社区 交流。