1

我有一张表pings,里面有大约 1500 万行。我在 postgres 9.2.4 上。它具有的相关列是一个外键monitor_id、一个created_at时间戳和一个response_time表示毫秒的整数。这是确切的结构:

     Column      |            Type             |                     Modifiers                      
-----------------+-----------------------------+----------------------------------------------------
 id              | integer                     | not null default nextval('pings_id_seq'::regclass)
 url             | character varying(255)      | 
 monitor_id      | integer                     | 
 response_status | integer                     | 
 response_time   | integer                     | 
 created_at      | timestamp without time zone | 
 updated_at      | timestamp without time zone | 
 response_body   | text                        | 
Indexes:
    "pings_pkey" PRIMARY KEY, btree (id)
    "index_pings_on_created_at_and_monitor_id" btree (created_at DESC, monitor_id)
    "index_pings_on_monitor_id" btree (monitor_id)

我想查询所有没有的响应时间NULL(90% 不会NULL,大约 10% 会NULL),具有特定的monitor_id,并且是在上个月创建的。我正在使用 ActiveRecord 进行查询,但最终结果如下所示:

SELECT "pings"."response_time"
FROM "pings"
WHERE "pings"."monitor_id" = 3
AND (created_at > '2014-03-03 20:23:07.254281'
AND response_time IS NOT NULL)

这是一个非常基本的查询,但运行大约需要 2000 毫秒,这似乎相当慢。我假设索引会使它更快,但我尝试过的所有索引都不起作用,我假设这意味着我没有正确索引。

当我运行时EXPLAIN ANALYZE,这就是我得到的:

Bitmap Heap Scan on pings  (cost=6643.25..183652.31 rows=83343 width=4) (actual time=58.997..1736.179 rows=42063 loops=1)
  Recheck Cond: (monitor_id = 3)
  Rows Removed by Index Recheck: 11643313
  Filter: ((response_time IS NOT NULL) AND (created_at > '2014-03-03 20:23:07.254281'::timestamp without time zone))
  Rows Removed by Filter: 324834
  ->  Bitmap Index Scan on index_pings_on_monitor_id  (cost=0.00..6622.41 rows=358471 width=0) (actual time=57.935..57.935 rows=366897 loops=1)
        Index Cond: (monitor_id = 3)

所以最后有一个索引monitor_id正在使用,但没有别的。我已经使用monitor_id,created_at和尝试了复合索引的各种排列和顺序response_time。我试过按created_at降序排列索引。我已经尝试使用response_time IS NOT NULL.

我没有尝试过使查询更快。您将如何优化和/或索引它?

4

1 回答 1

2

列的顺序

使用正确的列序列创建部分多列索引。你有一个:

"index_pings_on_created_at_and_monitor_id" btree (created_at DESC, monitor_id)

但是列的顺序并不能很好地为您服务。反转它:

CREATE INDEX idx_pings_monitor_created ON pings (monitor_id, created_at DESC)
WHERE response_time IS NOT NULL;

这里的经验法则是:先相等,后取范围。更多相关信息:多
列索引和性能

正如所讨论的那样,这种情况WHERE response_time IS NOT NULL不会给你带来太多好处。如果您有其他可以利用此索引的查询,包括NULL中的值response_time,请将其删除。否则,保留它。

您可能还可以删除其他两个现有索引。有关 btree 索引中列序列的更多信息:
PostgreSQL 中的索引工作

覆盖指数

如果您只需要从表中获取response_time,那么这可能会快得多 - 如果您没有对表的行进行大量写入操作。在最后一个位置包含索引中的列以允许仅索引扫描(使其成为“覆盖索引”):

CREATE INDEX idx_pings_monitor_created
ON     pings (monitor_id, created_at DESC, response_time)
WHERE  response_time IS NOT NULL;  -- maybe

或者,你甚至试试这个..

更激进的部分索引

创建一个微小的辅助函数。实际上是数据库中的“全局常量”:

CREATE OR REPLACE FUNCTION f_ping_event_horizon()
  RETURNS timestamp LANGUAGE sql IMMUTABLE COST 1 AS
$$SELECT '2014-03-03 0:0'::timestamp$$;  -- One month in the past

将其用作索引中的条件:

CREATE INDEX idx_pings_monitor_created_response_time
ON     pings (monitor_id, created_at DESC, response_time)
WHERE  response_time IS NOT NULL  -- maybe
AND   created_at > f_ping_event_horizon();

您的查询现在看起来像这样:

SELECT response_time
FROM   pings
WHERE  monitor_id = 3
AND    response_time IS NOT NULL
AND    created_at > '2014-03-03 20:23:07.254281'
AND    created_at > f_ping_event_horizon();

旁白:我修剪了一些噪音。

最后一个条件在逻辑上似乎是多余的。只包含它,如果 Postgres 不理解它可以使用没有它的索引。可能是必要的。条件中的实际时间戳必须大于函数中的时间戳。但是根据您的评论显然是这种情况。

通过这种方式,我们删除了所有不相关的行并使索引更小。随着时间的推移,效果会缓慢下降。重新调整事件视界并不时重新创建索引以消除增加的权重。例如,您可以使用每周一次的 cron 作业。

在更新(重新创建)函数时,您需要重新创建以任何方式使用该函数的所有索引。最好在同一笔交易中。因为IMMUTABLE辅助函数的声明有点虚假。但是 Postgres 只接受索引定义中的不可变函数。所以我们不得不撒谎。更多相关信息:
PostgreSQL 是否支持“不区分重音”排序规则?

为什么要使用该功能?这样,所有使用索引的查询都可以保持不变。

With all of these changes the query should be faster by orders of magnitude now. A single, continuous index-only scan is all that's needed. Can you confirm that?

于 2014-04-03T21:33:57.970 回答