【问题标题】:Why does the following Postgres SQL query take so long?为什么以下 Postgres SQL 查询需要这么长时间?
【发布时间】:2011-05-30 09:37:59
【问题描述】:

原始查询如下

SELECT "TIME", "TRADEPRICE"
  FROM "YEAR" where "DATE"='2010-03-01' 
  and "SECURITY"='STW.AX' 
  AND "TIME" < '10:16:00' 
  AND "TYPE" = 'TRADE'
  ORDER BY "TIME" ASC LIMIT 3

我已经建立了三个索引如下

Columns "DATE" DESC NULLS LAST
Columns "SECURITY" DESC NULLS LAST
Columns "TIME" DESC NULLS LAST

我不索引 TYPE,因为它只取两个可能值之一

解释分析产生以下结果

"Limit  (cost=50291.28..50291.28 rows=3 width=16) (actual time=1794484.566..1794484.567 rows=3 loops=1)"
"  ->  Sort  (cost=50291.28..50291.29 rows=4 width=16) (actual time=1794484.562..1794484.563 rows=3 loops=1)"
"        Sort Key: "TIME""
"        Sort Method:  top-N heapsort  Memory: 25kB"
"        ->  Bitmap Heap Scan on "YEAR"  (cost=48569.54..50291.24 rows=4 width=16) (actual time=1794411.662..1794484.498 rows=20 loops=1)"
"              Recheck Cond: (("SECURITY" = 'STW.AX'::bpchar) AND ("DATE" = '2010-03-01'::date))"
"              Filter: (("TIME" < '10:16:00'::time without time zone) AND ("TYPE" = 'TRADE'::bpchar))"
"              ->  BitmapAnd  (cost=48569.54..48569.54 rows=430 width=0) (actual time=1794411.249..1794411.249 rows=0 loops=1)"
"                    ->  Bitmap Index Scan on security_desc  (cost=0.00..4722.94 rows=166029 width=0) (actual time=1793917.506..1793917.506 rows=1291933 loops=1)"
"                          Index Cond: ("SECURITY" = 'STW.AX'::bpchar)"
"                    ->  Bitmap Index Scan on date_desc  (cost=0.00..43846.35 rows=2368764 width=0) (actual time=378.698..378.698 rows=2317130 loops=1)"
"                          Index Cond: ("DATE" = '2010-03-01'::date)"
"Total runtime: 1794485.224 ms"

该数据库大约有 10 亿行运行在 Core2Quad 上,在 Ubuntu 64 位上具有 8gig RAM。这个查询肯定不会花半个小时

【问题讨论】:

  • 是这三个独立的索引还是一个与那些列?我会创建一个包含 DATE 和 SECURITY 的索引。 (我对 Postgres 的了解还不够,无法回答这个问题。)
  • 可以添加表定义吗?
  • @deltanovember: explain.depesz.com/s/VqC
  • 很少需要TIME 上的单个索引。你可以安全地放下它。然后也删除DATE 上的索引并在(DATE, TIME) 上添加一个索引,这更有可能在其他查询中也有用。
  • 这可能需要一段时间,但对表运行 VACUUM ANALYZE 以更新表统计信息。

标签: sql postgresql


【解决方案1】:

该数据库约有 10 亿行运行在 Core2Quad 上,在 Ubuntu 64 位上具有 8gig RAM。这个查询肯定不需要半个小时

由于您设置索引的方式,这需要半小时。

您的查询没有可用于直接转到所需行的多列索引。它做了次好的事情,即对几乎没有选择性的索引进行位图索引扫描,并对结果集进行前 3 排序。

关于证券和日期的两个索引分别产生 1.3M 和 2.3M 行。将它们组合起来会非常缓慢,因为您会随机查找超过一百万行并过滤每一行。

雪上加霜的是,您的数据结构使得两个高度相关的字段(日期和时间)分别存储和操作。这让查询计划者感到困惑,因为 Postgres 不收集相关数据。因此,您的查询几乎总是会通过大量数据集进行过滤,并根据单独的标准对过滤后的数据集进行排序。

我建议进行以下更改:

  1. 更改表并添加日期时间列,类型为 timestamp with time zone。将您的日期和时间列合并到其中。

  2. 相应地删除日期和时间字段,以及它们上的索引。还要删除安全指数。

  3. 在(安全、日期时间)上创建索引。 (除非您的排序标准也包含这些子句,否则不要乱用 nulls first/nulls last。)

  4. 如果您需要执行查询以统计一天或日期范围内的所有交易,您可以选择在 (datetime) 或 (datetime, security) 添加单独的索引。

  5. 完成上述操作后,对整个混乱进行真空分析。

然后您就可以像这样重写您的查询:

SELECT "TIME", "TRADEPRICE"
  FROM "YEAR"
  WHERE '2010-03-01 00:00:00' <= "DATETIME" AND "DATETIME" < '2010-03-01 10:16:00'
  AND "SECURITY"='STW.AX' 
  AND "TYPE" = 'TRADE'
  ORDER BY "DATETIME" ASC LIMIT 3

这将产生最优化的计划:从 (security, datetime) 上的过滤索引扫描中检索前 3 行,我预计(因为您有 10 亿行)最多需要 25 毫秒。

【讨论】:

  • 时间戳和更正索引的明确 +1。你的版本应该会飞。 (也可能在TYPE 上划分为两个表。)
  • 另一个非侵入式选项是创建一个IMMUTABLE pl/pgsql 查找FUNCTION,它返回串联的DATETIME 字段。然后,使用您的查找 FUNCTION 创建一个 INDEX。事实证明,这在您无法轻松更改架构的紧要关头非常有效。请务必编写查询以使用查找 FUNCTION 否则您将看不到任何好处。
  • +1 肖恩。如果表格无法更改,这是一个不错的选择。
【解决方案2】:

将多个搜索词组合在一起添加一个复合索引,例如ON YEAR (TYPE, SECURITY, DATE, TIME)。然后数据库可以在单个索引中查找以匹配所有这些,而不必搜索多个索引并将所有结果整理在一起(位图索引扫描)。

究竟哪些列(例如是否包含TYPE?)以及将它们包含在索引中的顺序取决于数据特征和您正在执行的其他类型的查询(因为您可以重用任何left-免费的复合索引的子集),所以尝试一下;但为了鼓励顺序优化,请保留 ORDER BY 列作为最后使用的索引列/方向。

您可能还想ANALYZE 更新查询规划器的统计信息,因为某些行数猜测似乎有点不对劲。

【讨论】:

  • 问题是每个索引已经 ~30gb 我实际上没有足够的磁盘空间来添加更多索引。数据库几乎吞噬了我的整个硬盘
  • 更多磁盘可能比您的时间更便宜。您可以添加一个快速(10,000 rpm?)磁盘以开始(300 美元?),然后将新索引放在该磁盘上的表空间中。稍后,当您有更多的金钱和时间时,您可以将其重新配置为 RAID 设置或移动其他索引(如果有意义)。 (警告:我对十亿行表没有任何经验。我最大的是大约 2.5 亿。)
【解决方案3】:

我没有索引 TYPE,因为它只取两个可能值之一

您必须了解索引的工作原理以及它们的工作原理。索引将索引数据复制到仅包含指定索引数据的精简小型索引块中。从您的 X GB 原始数据中,仅保留 X/20(估计)大小。如果您指定的查询使用未编入索引的数据,则意味着对于满足其他查询条件的每条记录,DBMS 必须将相应的原始数据块读取到索引块以确定其是否匹配查询条件。

最佳情况是至少有一个索引包含查询状态的每一个要求,因此无需查找数据块。

另一个提示:通常最好列出取值的列,这些值通常在最后一个范围(在您的情况下为“TIME”)查询。

我的建议:删除所有索引。使用字段 TIME(ASC)、DATE、SECURITY、TYPE(按此顺序)创建索引。使用查询

SELECT "TIME", "TRADEPRICE"
  FROM "YEAR"
  WHERE "TIME" < '10:16:00'
  AND "DATE"='2010-03-01' 
  AND "SECURITY"='STW.AX' 
  AND "TYPE" = 'TRADE'

并观看令人难以置信的速度。

【讨论】:

  • 您说 '通常最好列出采用通常作为范围查询的值的列(在您的情况下为“TIME”)last。 ' 然后你把TIME放在索引的第一位。你的建议是哪两个?
  • 因为您需要按时间排序,所以我先添加了时间。通常,如果索引满足您的排序选项(这就是我在这里所做的),您将不需要特殊排序。这样,您不需要特殊的 ORDER BY 子句。
  • 在这种情况下,您仍然应该包含 ORDER BY 以明确说明您想要什么(SQL 不保证您总是会得到它);如果它与您的索引排序匹配,您将“免费”获得它。但是,是的,它应该是查询中使用的索引左子集的最右边(最不重要)列。
  • 好的。但我认为您的第一个建议很好,查​​询将从SECURITY, TYPE, DATE, TIME 索引中受益更多而不是TIME, ... 索引。
  • 你可以试试看。以一种方式创建索引,显式使用 ORDER BY 并对其计时,然后尝试另一种方式。我总是希望 TIME 成为最后一个索引列。
猜你喜欢
  • 2021-11-27
  • 2015-08-11
  • 2011-08-27
  • 1970-01-01
  • 1970-01-01
  • 2022-11-10
  • 1970-01-01
  • 2014-05-31
相关资源
最近更新 更多