什么时候使用index_scan来获取已排序的数据?

时间:2015-10-18 07:17:29

标签: sql postgresql sorting indexing

有一些经验法则吗?问题是我刚刚提出了这个问题,其中索引无法通过预定义的排序更快地运行查询。我有下表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_tablespg_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

问题: 是否有一个或多或少的短规则,在哪里使用索引进行排序以及哪些方法不会很好。是否我的表使用索引真的不适合避免排序?

2 个答案:

答案 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以生成整个数据集。

根据您对7s6.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将仅扫描此索引以获取不同的组,而不进行排序。