我有一张带有>的表格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...)
)
当我想SELECT
某个日期的所有行时,查询太慢了;需要12秒到20秒。我的目标是最多需要1秒。我希望查询返回表中包含的行的0.1%和1%之间。
查询非常简单:
SELECT * FROM all_legs WHERE dep_dt::date = '2017-08-15' ORDER BY price_ct ASC
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
注意:我昨天学到了这个命令,所以我仍然无法完全理解所有返回的内容。
我已尝试使用index-only scans
,如建议的here,运行命令:CREATE index idx_all_legs on all_legs(dep_dt);
,但我没有注意到运行时间的任何差异。我也尝试为所有列创建索引,因为我希望所有列都返回。
另一个想法是按dep_dt
排序所有行,因此搜索满足条件的所有行应该快得多,因为它们不会分散。不幸的是,我不知道如何实现这一点。
有没有办法让它像我的目标一样快?
根据Laurenz' answer的建议,通过添加索引CREATE INDEX IF NOT EXISTS idx_dep_dt_price ON all_legs(dep_dt, price_ct);
并将SELECT
中的条件调整为WHERE dep_dt >= '2017-08-15 00:00:00' AND dep_dt < '2017-08-16 00:00:00'
,可将运行时间缩短至1/4。即使这是一个非常好的改进,这意味着运行时间在2到6秒之间。
任何进一步减少运行时间的想法都将受到赞赏。
答案 0 :(得分:8)
索引不会有帮助。
两种解决方案:
您可以将查询更改为:
WHERE dep_dt >= '2017-08-15 00:00:00' AND dep_dt < '2017-08-16 00:00:00'
然后可以使用索引。
在表达式上创建索引:
CREATE INDEX ON all_legs(((dep_dt AT TIME ZONE 'UTC')::date));
(或其他时区)并将查询更改为
WHERE (dep_dt AT TIME ZONE 'UTC')::date = '2017-08-16'
AT TIME ZONE
是必要的,否则投射的结果将取决于您当前的TimeZone
设置。
第一个解决方案更简单,但第二个解决方案的优势在于您可以将price_ct
添加到索引中,如下所示:
CREATE INDEX ON all_legs(((dep_dt AT TIME ZONE 'UTC')::date), price_ct);
然后你不再需要排序了,你的查询将在理论上得到最快的速度。
答案 1 :(得分:2)
索引无效,因为您使用
WHERE dept_dt::date=constant
这对初学者来说似乎很好,但对于数据库来说,它看起来像是:
WHERE convert_timestamp_to_date(dep_ts)=constant
使用convert_timestamp_to_date()作为一个任意函数(我只想出了这个名字,不要在文档中查找)。为了在dep_ts上使用索引,DB必须将convert_timestamp_to_date函数反转为convert_date_to_timestamp_range(因为日期对应于时间戳的范围,而不仅仅是一个时间戳),然后像Laurenz那样重写WHERE。 p>
由于有许多这样的功能,数据库开发人员并没有费心去维护一个如何反转它们的巨大表格。它也只对特殊情况有帮助。例如,如果您在WHERE中指定了日期范围而不是“=常量”,那么这将是另一种特殊情况。这是你的工作;)
此外,(dep_dt,price_ct)上的索引不会加速排序,因为第一列是时间戳,因此行不按您希望的方式在索引中排序。你需要一个索引(dept_dt :: date,price_ct)来消除排序。
现在,要创建哪个索引?这取决于......
如果您还使用时间戳范围查询,例如“WHERE dep_dt BETWEEN ... AND ...”,那么dep_dt上的索引需要是原始时间戳类型。在这种情况下,在同一列上创建另一个索引但转换为日期是不必要的(所有索引都必须在写入时更新,因此不必要的索引会减慢插入/更新的速度)。但是,如果您在(dep_ts :: date,price_ct)上使用索引很多次并且消除排序对您来说非常重要,那么它可能是有意义的。这是一个权衡。
答案 2 :(得分:1)
您应该在此更改的第一步是删除复合 primary key
并使用普通的一列而不是此。
这是因为如果你要使用一些列索引,它最好用一个列整数索引,就像一个脊椎,并允许你的索引获取你需要的快速行。如果您的示例中有如此大的索引,那么规划师可能会说他扫描整个表格会更快。
即使您的索引足够好以供规划人员使用,也可以通过订购来删除。我说'可能'就像 - 在sql中的许多东西一样 - 它取决于你在表格中的实际数据,分析等等。
我不确定Postgres,但您可能想尝试在order by
中使用的列上构建另一个索引,甚至尝试为(dep_dt, price_ct)
尝试复合索引。您也可以尝试将dep_dt
放到order by
列表中,以便为编译器提供提示。
您需要此表中的所有吗?使用*
vs id
(例如)也会产生影响。
dep_dt
列中的唯一值如何?有时计划者可以说通过整个表格扫描可能比通过索引更有效,因为这里有许多非唯一值。
总之, SQL查询是实验的艺术,因为它完全取决于当前数据(因为规划人员使用分析器构建的统计数据来猜测最佳查询计划)。因此,甚至可能会发生这样的情况:当您将查询调整到具有数千行的表时,当您达到数百万行时,它可能会停止工作。