The*_*eOx 7 sql postgresql query-optimization
我在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'
Run Code Online (Sandbox Code Playgroud)
解释分析的输出是:
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
Run Code Online (Sandbox Code Playgroud)
我认为问题在于位置表上的顺序扫描,但我无法弄清楚它为什么存在.带索引的表结构如下:
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)
Run Code Online (Sandbox Code Playgroud)
我想不出任何其他方式来重写查询,所以我很难过.如果当前的设置尽可能好,那么在我看来它应该比现在快得多.任何帮助将非常感激.
您进行顺序扫描的原因是 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, ...)
Run Code Online (Sandbox Code Playgroud)
这里的点代表您选择的其余列。您只需要一个位置索引,但很难判断哪个索引是最好的。第一种方法可能允许对预排序的数据进行合并连接,但需要读取整个第二个索引来进行过滤。第二个和第三个将允许对数据进行预过滤,但需要哈希连接。考虑到散列连接的成本有多少,合并连接很可能是一个不错的选择。
由于您的查询需要每行 87 个字节中的 52 个字节,并且索引具有开销,因此您最终可能不会发现索引占用的空间(如果有的话)比表本身更少。
另一种方法是通过查看聚类来攻击它的“随机分布”方面。
| 归档时间: |
|
| 查看次数: |
564 次 |
| 最近记录: |