【问题标题】:When is it appropriate to use index_scan for getting data already sorted?什么时候适合使用 index_scan 来获取已经排序的数据?
【发布时间】:2016-01-16 16:10:32
【问题描述】:

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

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

【问题讨论】:

  • 您完全扫描超过 500k 行,两次。这需要一些时间。 PS:对应的EXPLAIN ANALYZE是什么?该指数提高了成本:总体提高了两倍,第一行降至 0。这是一项重大改进。
  • @zerkms 你是什么意思解释分析?我提供了关于查询的更完整的统计信息,而不仅仅是 EXPLAIN (ANALYZE, BUFFERS)
  • 您的查询消除了排序,分析表明这一点。你的问题到底是什么? “你是什么意思解释分析?” --- 我的意思是字面意思,EXPLAIN ANALYZE 的输出是什么?
  • Explain analyze for seq scan 表明它还需要 71MB 来对表执行快速排序。你是这个意思吗?
  • 不,我很好奇第一行可用的确切时间。如果是 ~0ms5s,那么后一种解决方案 IS 是一种改进(我更不明白你为什么问第二个问题)

标签: sql postgresql sorting indexing


【解决方案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 主题对我来说有点模糊,所以我个人更喜欢先在我的数据集上检查它。

【讨论】:

  • 非常有趣的关于仅索引扫描的 wiki 页面,非常感谢。
【解决方案2】:

如果您使用的版本 >= 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 标准的一部分,
目前——据我所知——它们由 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 只会扫描该索引以获取不同的组,而不进行排序。

【讨论】:

  • 如果我错了,请纠正我。我认为在查询中使用 DISTINCT 并不太快,因为它需要对数据进行排序(据我所知,不能使用散列)。那么,这样的查询不会增加额外的开销吗?此外,如果您提供一些对 LATERAL 及其工作原理的参考,我们将不胜感激。我以前没有遇到过。
  • 我已经用一些额外的信息更新了答案,请看一下。
  • “由于您的查询仅从 598221 中选择了 2991 行” --- 它选择了 250k+。 2991 是一个估计值。
猜你喜欢
  • 1970-01-01
  • 2011-09-13
  • 2016-06-16
  • 2010-10-08
  • 1970-01-01
  • 1970-01-01
  • 2012-06-13
  • 1970-01-01
  • 2020-08-07
相关资源
最近更新 更多