PostgreSQL:加速表中数百万行的SELECT查询

时间:2017-07-26 07:39:52

标签: 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...)
                )

当我想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秒之间。

任何进一步减少运行时间的想法都将受到赞赏。

3 个答案:

答案 0 :(得分:8)

索引不会有帮助。

两种解决方案:

  1. 您可以将查询更改为:

    WHERE dep_dt >= '2017-08-15 00:00:00' AND dep_dt < '2017-08-16 00:00:00'
    

    然后可以使用索引。

  2. 在表达式上创建索引:

    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设置。

  3. 第一个解决方案更简单,但第二个解决方案的优势在于您可以将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)

  1. 您应该在此更改的第一步是删除复合 primary key并使用普通的一列而不是此。 这是因为如果你要使用一些列索引,它最好用一个列整数索引,就像一个脊椎,并允许你的索引获取你需要的快速行。如果您的示例中有如此大的索引,那么规划师可能会说他扫描整个表格会更快。

  2. 即使您的索引足够好以供规划人员使用,也可以通过订购来删除。我说'可能'就像 - 在sql中的许多东西一样 - 它取决于你在表格中的实际数据,分析等等。 我不确定Postgres,但您可能想尝试在order by中使用的列上构建另一个索引,甚至尝试为(dep_dt, price_ct)尝试复合索引。您也可以尝试将dep_dt放到order by列表中,以便为编译器提供提示。

  3. 您需要此表中的所有吗?使用* vs id(例如)也会产生影响。

  4. dep_dt列中的唯一值如何?有时计划者可以说通过整个表格扫描可能比通过索引更有效,因为这里有许多非唯一值

  5. 总之, SQL查询是实验的艺术,因为它完全取决于当前数据(因为规划人员使用分析器构建的统计数据来猜测最佳查询计划)。因此,甚至可能会发生这样的情况:当您将查询调整到具有数千行的表时,当您达到数百万行时,它可能会停止工作。