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

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

该指数无济于事.

两种解决方案

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

    WHERE dep_dt >= '2017-08-15 00:00:00' AND dep_dt < '2017-08-16 00:00:00'
    
    Run Code Online (Sandbox Code Playgroud)

    然后可以使用索引.

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

    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)

然后你不再需要排序了,你的查询将在理论上得到最快的速度.


peu*_*feu 5

该索引没有帮助,因为您使用

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) 上的索引,并且消除排序对您来说非常重要,那么它可能是有意义的。这是一个权衡。