有一些经验法则吗?问题是我刚刚提出了这个问题,其中索引无法通过预定义的排序更快地运行查询。我有下表tbl
:
| p_id | s_id | w_id | amount | currency_id | date |
|integer | integer | integer | numeric | integer | timestamp without time zone|
该表包含大约500k
行,我需要对其执行以下查询:
SELECT p_id, s_id, w_id, amount, currency_id
FROM (
SELECT p_id, s_id, w_id, amount, currency_id,
ROW_NUMBER() OVER(PARTITION BY p_id, s_id, w_id ORDER BY date DESC NULLS LAST) rn
FROM tbl
) sbt
WHERE sbt.rn = 1
如果表格中没有任何索引,规划人员会选择以下操作:
Subquery Scan on sbt (cost=68369.47..90802.76 rows=2991 width=19) (actual time=616.402..958.030 rows=253657 loops=1)
Filter: (sbt.rn = 1)
Rows Removed by Filter: 344564
-> WindowAgg (cost=68369.47..83324.99 rows=598221 width=27) (actual time=616.397..909.711 rows=598221 loops=1)
-> Sort (cost=68369.47..69865.02 rows=598221 width=27) (actual time=616.384..642.357 rows=598221 loops=1)
Sort Key: tbl.p_id, tbl.s_id, tbl.w_id, tbl.date
Sort Method: quicksort Memory: 71313kB
-> Seq Scan on tbl (cost=0.00..10969.21 rows=598221 width=27) (actual time=0.038..111.827 rows=598221 loops=1)
Total runtime: 967.421 ms
在我的数据上平均需要7秒。我认为,排序是一项非常昂贵的操作,因此使用index scan
而不是seq scan + sort
更好。但是,如果我创建一个合适的索引:
CREATE INDEX text_idx
ON tbl
USING btree
(p_id, s_id, w_id, date DESC NULLS LAST, currency_id, amount);
为了Index Only Scan
,计划将如下:
Subquery Scan on sbt (cost=0.00..56853.58 rows=2991 width=19) (actual time=167.895..747.224 rows=253657 loops=1)
Filter: (sbt.rn = 1)
Rows Removed by Filter: 344564
-> WindowAgg (cost=0.00..49375.82 rows=598221 width=27) (actual time=167.889..693.238 rows=598221 loops=1)
-> Index Only Scan using test_idx_to_drop on tbl (cost=0.00..35915.84 rows=598221 width=27) (actual time=167.876..365.174 rows=598221 loops=1)
Heap Fetches: 598221
Total runtime: 752.713 ms
看起来很棒,但它对提高性能没有太大帮助。查询执行的平均时间现在是6.8秒。我开始研究表格的I / O统计信息(pg_statio_user_tables
,pg_stat_user_table
),我发现的是:
在index scan
的情况下,这里是统计信息(冷缓存):
idx_scan idx_tup_fetch heap_blks_read idx_blk_read
1 598221 4987 3819
和sort+seq
扫描
seq_scan seq_tup_read heap_blks_read
1 598221 4987
问题: 是否有一个或多或少的短规则,在哪里使用索引进行排序以及哪些方法不会很好。是否我的表使用索引真的不适合避免排序?
答案 0 :(得分:1)
如果我们比较EXPLAIN ANALYZE
的输出,除了成本提供实际时间外,我们会看到来自
Subquery Scan on sbt <skipped> (actual time=616.402..958.030 rows=253657 loops=1)
<skipped>
Total runtime: 967.421 ms
已改善为
Subquery Scan on sbt <skipped> (actual time=167.895..747.224 rows=253657 loops=1)
<skipped>
Total runtime: 752.713 ms
这意味着初始查询已从~616ms
改进为~168ms
以获得第一行,并从~967ms
改进为~753ms
以生成整个数据集。
根据您对7s
和6.8s
的观察,获取客户端上的整个数据集是因为通过网络传输数十万行需要时间:延迟和带宽是有限的,有限的,因此在数据传递方面会带来一些延迟。
除了我所说的,我建议你尝试将索引减少到p_id, s_id, w_id, date DESC NULLS LAST
列,看看性能是否仍然可以接受。 index-only scans主题对我来说有点模糊,所以我个人更喜欢先在我的数据集上查看它。
答案 1 :(得分:1)
如果您使用的是版本&gt; = 9.3,则可以尝试使用横向子查询:
SELECT x.*
FROM (
SELECT DISTINCT p_id, s_id, w_id FROM tbl
) t,
LATERAL (
SELECT p_id, s_id, w_id, amount, currency_id
FROM tbl t1
WHERE t1.p_id = t.p_id AND t1.s_id = t.s_id AND t1.w_id = t.w_id
ORDER BY p_id, s_id, w_id, date DESC NULLS LAST
LIMIT 1
) x
;
仅在列的子集上使用新索引:
CREATE INDEX text_idx_new
ON tbl
USING btree
(p_id, s_id, w_id, date DESC NULLS LAST);
横向子查询是SQL Standard的一部分,
目前 - 据我所知 - 它们由Oracle 12c,IMB DB2,MS SQL Server和PostgreSQL 9.3实现。
您可以在此处找到文档:http://www.postgresql.org/docs/9.3/static/queries-table-expressions.html
主题名称: 7.2.1.5。横向子查询
横向子查询使用ORDER BY ... LIMIT 1
,PostgreSql可以使用索引来优化这种查询(不幸的是我无法找到关于PostgreSql这个主题的任何文档,他们的文档与Oracle相比较差他们有关于优化器如何工作的非常详细的文档
由于您的查询仅从598221中选择了2991行,即0.4%(非常少),因此这意味着只有2991个不同的grop,并且横向子查询将仅执行2991次。
我认为值得尝试一下,因为它可能表现得更好。
你是对的,DISTINCT p_id, s_id, w_id
需要对数据进行排序,但是在这些列上创建了索引,并且此索引包含已排序的数据,因此PostgreSql将仅扫描此索引以获取不同的组,而不进行排序。