【问题标题】:Optimize postgresql query优化postgresql查询
【发布时间】:2012-12-04 21:57:27
【问题描述】:

我在 PostgreSQL 9.1 中有 2 个表 - flight_2012_09_12 包含大约 500,000 行,而 position_2012_09_12 包含大约 550 万行。我正在运行一个简单的连接查询,它需要很长时间才能完成,尽管这些表并不小,但我相信在执行过程中会有一些重大收获。

查询是:

SELECT f.departure, f.arrival, 
       p.callsign, p.flightkey, p.time, p.lat, p.lon, p.altitude_ft, p.speed 
FROM position_2012_09_12 AS p 
JOIN flight_2012_09_12 AS f 
     ON p.flightkey = f.flightkey 
WHERE p.lon < 0 
      AND p.time BETWEEN '2012-9-12 0:0:0' AND '2012-9-12 23:0:0'

解释分析的输出是:

Hash Join  (cost=239891.03..470396.82 rows=4790498 width=51) (actual time=29203.830..45777.193 rows=4403717 loops=1)
Hash Cond: (f.flightkey = p.flightkey)
->  Seq Scan on flight_2012_09_12 f  (cost=0.00..1934.31 rows=70631 width=12) (actual time=0.014..220.494 rows=70631 loops=1)
->  Hash  (cost=158415.97..158415.97 rows=3916885 width=43) (actual time=29201.012..29201.012 rows=3950815 loops=1)
     Buckets: 2048  Batches: 512 (originally 256)  Memory Usage: 1025kB
     ->  Seq Scan on position_2012_09_12 p  (cost=0.00..158415.97 rows=3916885 width=43) (actual time=0.006..14630.058 rows=3950815 loops=1)
           Filter: ((lon < 0::double precision) AND ("time" >= '2012-09-12 00:00:00'::timestamp without time zone) AND ("time" <= '2012-09-12 23:00:00'::timestamp without time zone))
Total runtime: 58522.767 ms

我认为问题出在位置表上的顺序扫描上,但我不知道为什么会出现。带索引的表结构如下:

               Table "public.flight_2012_09_12"
   Column       |            Type             | Modifiers 
--------------------+-----------------------------+-----------
callsign           | character varying(8)        | 
flightkey          | integer                     | 
source             | character varying(16)       | 
departure          | character varying(4)        | 
arrival            | character varying(4)        | 
original_etd       | timestamp without time zone | 
original_eta       | timestamp without time zone | 
enroute            | boolean                     | 
etd                | timestamp without time zone | 
eta                | timestamp without time zone | 
equipment          | character varying(6)        | 
diverted           | timestamp without time zone | 
time               | timestamp without time zone | 
lat                | double precision            | 
lon                | double precision            | 
altitude           | character varying(7)        | 
altitude_ft        | integer                     | 
speed              | character varying(4)        | 
asdi_acid          | character varying(4)        | 
enroute_eta        | timestamp without time zone | 
enroute_eta_source | character varying(1)        | 
Indexes:
"flight_2012_09_12_flightkey_idx" btree (flightkey)
"idx_2012_09_12_altitude_ft" btree (altitude_ft)
"idx_2012_09_12_arrival" btree (arrival)
"idx_2012_09_12_callsign" btree (callsign)
"idx_2012_09_12_departure" btree (departure)
"idx_2012_09_12_diverted" btree (diverted)
"idx_2012_09_12_enroute_eta" btree (enroute_eta)
"idx_2012_09_12_equipment" btree (equipment)
"idx_2012_09_12_etd" btree (etd)
"idx_2012_09_12_lat" btree (lat)
"idx_2012_09_12_lon" btree (lon)
"idx_2012_09_12_original_eta" btree (original_eta)
"idx_2012_09_12_original_etd" btree (original_etd)
"idx_2012_09_12_speed" btree (speed)
"idx_2012_09_12_time" btree ("time")

          Table "public.position_2012_09_12"
Column    |            Type             | Modifiers 
-------------+-----------------------------+-----------
 callsign    | character varying(8)        | 
 flightkey   | integer                     | 
 time        | timestamp without time zone | 
 lat         | double precision            | 
 lon         | double precision            | 
 altitude    | character varying(7)        | 
 altitude_ft | integer                     | 
 course      | integer                     | 
 speed       | character varying(4)        | 
 trackerkey  | integer                     | 
 the_geom    | geometry                    | 
Indexes:
"index_2012_09_12_altitude_ft" btree (altitude_ft)
"index_2012_09_12_callsign" btree (callsign)
"index_2012_09_12_course" btree (course)
"index_2012_09_12_flightkey" btree (flightkey)
"index_2012_09_12_speed" btree (speed)
"index_2012_09_12_time" btree ("time")
"position_2012_09_12_flightkey_idx" btree (flightkey)
"test_index" btree (lon)
"test_index_lat" btree (lat)

我想不出任何其他方式来重写查询,所以我现在很难过。如果当前的设置尽可能好,那就这样吧,但在我看来,它应该比现在快得多。任何帮助将不胜感激。

【问题讨论】:

  • 您能否提供有关 public.position_2012_09_12 表 lon 和 time 列的统计信息?也许某些(时间) lon
  • 您使用的是哪个版本的 Postgresql?
  • 在进行查询之前您是否分析过您的表?
  • 估计的和实际的行数看起来很匹配,所以我怀疑表需要分析。哈希连接上的 512 个批次看起来很大,而 1024kb 的内存使用量看起来很小——我想知道使用更大的 work_mem 是否会做得更好。除此之外,该计划对我来说看起来不错,而且性能可能只会随着硬件的改进而提高。
  • BETWEEN 可能没有做你想做的事——它将包括晚上 11:00 的第一秒/毫秒/纳秒(postgreSQL 运行的粒度);总是(几乎)使用独占上限(&lt;),尤其是时间戳。另外,为什么只有 11 点(直到第二天之前)? 'time' 是列的非描述性名称 - 可能使用类似 recordedAt 的名称?

标签: sql postgresql query-optimization


【解决方案1】:

行数估计相当合理,所以我怀疑这是一个统计问题。

我会尝试:

  • 如果您经常搜索lon &lt; 0,则在position_2012_09_12(lon,"time") 上创建索引或可能在position_2012_09_12("time") WHERE (lon &lt; 0) 上创建部分索引。

  • 设置 random_page_cost 更低,可能是 1.1。看看 (a) 这是否会改变计划,以及 (b) 新计划是否真的更快。为了测试目的,看看避免 seqscan 是否会更快,你可以SET enable_seqscan = off;如果是,请更改成本参数。

  • 为此查询增加work_memSET work_mem = 10M 或运行它之前的东西。

  • 如果您还没有运行最新的 PostgreSQL,请运行。始终在问题中指定您的 PostgreSQL 版本。 (编辑后更新):您使用的是 9.1;没关系。 9.2 中最大的性能改进是仅索引扫描,您似乎不太可能从该查询的仅索引扫描中受益匪浅。

如果您可以去掉列来缩小行的范围,您也会在一定程度上提高性能。它不会有很大的不同,但会有所作为。

【讨论】:

    【解决方案2】:

    您获得顺序扫描的原因是 Postgres 认为它​​会比使用索引读取更少的磁盘页面。这可能是对的。考虑一下,如果使用非覆盖索引,则需要读取所有匹配的索引页面。它本质上是输出一个行标识符列表。然后数据库引擎需要读取每个匹配的数据页。

    您的位置表每行使用 71 个字节,加上 geom 类型占用的任何内容(为了说明,我假设 16 个字节),即 87 个字节。一个 Postgres 页面是 8192 字节。所以每页大约有 90 行。

    您的查询匹配 5563070 行中的 3950815 行,约占总数的 70%。假设数据是随机分布的,关于您的 where 过滤器,几乎有 30% ^ 90 的机会找到没有匹配行的数据页。这基本上没什么。因此,无论您的索引有多好,您仍然必须阅读所有数据页。如果您无论如何都必须阅读所有页面,那么表扫描通常是一个不错的方法。

    这里出来的,是我说的非覆盖索引。如果您准备创建可以自己回答查询的索引,则可以完全避免查找数据页面,这样您就可以重新开始游戏了。我建议以下值得一看:

    flight_2012_09_12 (flightkey, departure, arrival)
    position_2012_09_12 (filghtkey, time, lon, ...)
    position_2012_09_12 (lon, time, flightkey, ...)
    position_2012_09_12 (time, long, flightkey, ...)
    

    此处的点代表您选择的其余列。您只需要一个位置索引,但很难说哪个是最好的。第一种方法可能允许对预排序的数据进行合并连接,但代价是读取整个第二个索引以进行过滤。第二个和第三个将允许对数据进行预过滤,但需要哈希连接。给出散列连接的成本有多少,合并连接可能是一个不错的选择。

    由于您的查询需要每行 87 个字节中的 52 个字节,并且索引有开销,因此您最终可能不会使索引占用比表本身更少的空间(如果有的话)。

    另一种方法是通过查看聚类来攻击其“随机分布”的一面。

    【讨论】:

    • 在我看来不值得在飞行表上添加覆盖索引,因为完整扫描似乎只需要 220 毫秒?
    • @DavidAldridge 公平点,虽然在两个表上都有以飞行键开头的覆盖索引可以允许合并连接,我希望它比预排序数据上的哈希连接更快。
    • @DavidAldridge 用户使用的是 PostgreSQL 9.1,它没有仅索引扫描(如覆盖索引),因此问题仍然没有实际意义。
    • @CraigRinger 哦,这是 MVCC 问题吗?如果是这样,在 9.2 中会有所不同吗?
    • @DavidAldridge 您指的是索引中没有可见性信息的方式,因此 Pg 必须检查堆以确定行是否可见-这就是您所说的“ MVCC 问题”?如果是这样,9.2 添加了仅索引扫描,可以通过使用可见性映射来避免大多数堆访问;请参阅发行说明和wiki.postgresql.org/wiki/Index-only_scans
    猜你喜欢
    • 2014-06-13
    • 2022-01-12
    • 1970-01-01
    • 1970-01-01
    • 2022-11-12
    • 2018-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多