J0A*_*NMM 2 postgresql performance select
我有一个超过450万行的表,我的SELECT查询对我的需求来说太慢了.
该表创建于:
CREATE TABLE all_legs (
carrier TEXT,
dep_hub TEXT,
arr_hub TEXT,
dep_dt TIMESTAMP WITH TIME ZONE,
arr_dt TIMESTAMP WITH TIME ZONE,
price_ct INTEGER,
... 5 more cols ...,
PRIMARY KEY (carrier, dep_hub, arr_hub, dep_dt, arr_dt, ...3 other cols...)
)
Run Code Online (Sandbox Code Playgroud)
当我想要SELECT特定日期的所有行时,查询太慢; 需要12秒到20秒.我的目标是最多需要1秒.我希望查询返回表中包含的行的0.1%和1%之间.
查询非常简单:
SELECT * FROM all_legs WHERE dep_dt::date = '2017-08-15' ORDER BY price_ct ASC
Run Code Online (Sandbox Code Playgroud)
EXPLAIN ANALYZE 收益:
Sort (cost=197154.69..197212.14 rows=22982 width=696) (actual time=14857.300..14890.565 rows=31074 loops=1)
Sort Key: price_ct
Sort Method: external merge Disk: 5256kB
-> Seq Scan on all_legs (cost=0.00..188419.85 rows=22982 width=696) (actual time=196.738..14581.143 rows=31074 loops=1)
Filter: ((dep_dt)::date = '2017-08-15'::date)
Rows Removed by Filter: 4565249
Planning time: 0.572 ms
Execution time: 14908.274 ms
Run Code Online (Sandbox Code Playgroud)
注意:我昨天学到了这个命令,所以我仍然无法完全理解返回的所有内容.
我已经尝试使用index-only scans,如建议在这里,通过运行以下命令:CREATE index idx_all_legs on all_legs(dep_dt);但我没有注意到运行时间的任何差异.我也尝试为所有列创建索引,因为我希望所有列都返回.
另一个想法是对所有行进行排序dep_dt,因此搜索满足条件的所有行应该更快,因为它们不会被分散.不幸的是,我不知道如何实现这一点.
有没有办法让它像我的目标一样快?
正如Laurenz的回答所建议的那样,通过添加索引CREATE INDEX IF NOT EXISTS idx_dep_dt_price ON all_legs(dep_dt, price_ct);并调整SELECTto中的条件WHERE dep_dt >= '2017-08-15 00:00:00' AND dep_dt < '2017-08-16 00:00:00'已将运行时间减少到1/4.即使这是一个非常好的改进,这意味着运行时间在2到6秒之间.
任何进一步减少运行时间的想法都将被理解.
Lau*_*lbe 10
该指数无济于事.
两种解决方案
您可以将查询更改为:
WHERE dep_dt >= '2017-08-15 00:00:00' AND dep_dt < '2017-08-16 00:00:00'
Run Code Online (Sandbox Code Playgroud)
然后可以使用索引.
在表达式上创建索引:
CREATE INDEX ON all_legs(((dep_dt AT TIME ZONE 'UTC')::date));
Run Code Online (Sandbox Code Playgroud)
(或不同的时区)并将查询更改为
WHERE (dep_dt AT TIME ZONE 'UTC')::date = '2017-08-16'
Run Code Online (Sandbox Code Playgroud)
这AT TIME ZONE是必要的,否则演员的结果将取决于您当前的TimeZone设置.
第一个解决方案更简单,但第二个解决方案的优势在于您可以price_ct像这样添加到索引:
CREATE INDEX ON all_legs(((dep_dt AT TIME ZONE 'UTC')::date), price_ct);
Run Code Online (Sandbox Code Playgroud)
然后你不再需要排序了,你的查询将在理论上得到最快的速度.
该索引没有帮助,因为您使用
WHERE dept_dt::date=constant
Run Code Online (Sandbox Code Playgroud)
这对于初学者来说似乎很好,但对于数据库来说,它看起来像:
WHERE convert_timestamp_to_date(dep_ts)=constant
Run Code Online (Sandbox Code Playgroud)
由于convert_timestamp_to_date()是一个任意函数(我只是想出了这个名字,不要在文档中查找它)。为了使用dep_ts上的索引,数据库必须将函数convert_timestamp_to_date反转为类似convert_date_to_timestamp_range的函数(因为日期对应于一系列时间戳,而不仅仅是一个时间戳),然后像Laurenz那样重写WHERE。
由于有很多这样的函数,数据库开发人员没有费心去维护一个巨大的表来说明如何反转它们。此外,它仅对特殊情况有帮助。例如,如果您在 WHERE 中指定了日期范围而不是“=constant”,那么这将是另一种特殊情况。处理这个问题是你的工作;)
此外,(dep_dt,price_ct) 上的索引不会加快排序速度,因为第一列是时间戳,因此索引中的行不会按照您想要的方式排序。您需要 (dept_dt::date,price_ct) 上的索引来消除排序。
现在,要创建哪个索引?这取决于...
如果您还使用时间戳范围查询,例如“WHERE dep_dt BETWEEN ... AND ...”,那么 dep_dt 上的索引需要是原始时间戳类型。在这种情况下,在同一列上创建另一个索引,但转换为日期,将是不必要的(所有索引都必须在写入时更新,因此不必要的索引会减慢插入/更新速度)。但是,如果您多次使用 (dep_ts::date,price_ct) 上的索引,并且消除排序对您来说非常重要,那么它可能是有意义的。这是一个权衡。