如何在索引中使用 date_trunc() 和 timestamptz 来支持连接?

问题描述 投票:0回答:1

我的数据库中有 2 个表,定义如下:

CREATE TABLE metric_events (
    id uuid PRIMARY KEY,
    metric_id integer NOT NULL,
    event_at timestamp with time zone NOT NULL,
    code text NOT NULL, 
    test boolean DEFAULT false NOT NULL,
    success boolean DEFAULT false NOT NULL
);

CREATE TABLE hourly_metric_event_counters (
    start_at timestamp with time zone NOT NULL,
    metric_id integer NOT NULL,
    code text NOT NULL,
    test boolean NOT NULL,
    total_count integer DEFAULT 0,
    success_count integer DEFAULT 0,
    PRIMARY KEY (metric_id, test, code, start_at)
);

CREATE UNIQUE INDEX metric_events_uuid_index ON metric_events(id);
CREATE INDEX metric_events_event_at_index on metric_events(event_at);
CREATE INDEX metric_events_metric_id_and_event_at_index ON metric_events(metric_id, event_at);
CREATE INDEX hourly_metric_event_counters_start_at_index ON hourly_metric_event_counters(start_at);

我每天运行一次此查询:

SELECT date_trunc('hour', b.event_at),
       b.metric_id,
       b.code,
       b.test,
       COUNT(b.*) - MAX(bec.total_count) as total_diff,
       SUM(CASE WHEN b.success THEN 1 ELSE 0 END) - MAX(bec.success_count) as success_diff
FROM metric_events b
JOIN hourly_metric_event_counters bec
ON bec.metric_id = b.metric_id AND
   bec.test = b.test AND
   bec.code = b.code AND
   bec.start_at = date_trunc('hour', b.event_at)
WHERE event_at >= date_trunc('hour', NOW() - interval '24 hours') AND event_at < date_trunc('hour', NOW() - interval '1 hour')
GROUP BY 1, 2, 3, 4
HAVING COUNT(b.*) - MAX(bec.total_count) != 0 OR
       SUM(CASE WHEN b.success THEN 1 ELSE 0 END) - MAX(bec.success_count) != 0

我正在尝试在

metric_events
上为列
metric_id, date_trunc('hour', event_at), test, code
创建索引以使连接更快:

CREATE INDEX metric_events_composite_index on metric_events (metric_id, code, test, success, date_trunc('hour'::text, event_at));

但是:

错误:索引表达式中的函数必须标记为 IMMUTABLE SQL 状态:42P17

有办法吗?

postgresql indexing postgresql-performance volatility
1个回答
0
投票

该函数有多个(重载)版本

date_trunc()
。所有这些都是
IMMUTABLE
- 除了采用
timestamptz
输入的那个(必然)只是
STABLE
。某些调用(如
date_trunc('day', event_at)
)取决于当前
time_zone
设置。甚至还有(极其愚蠢的)时间转换,涉及(同样愚蠢的)“夏令时”的不到一小时。因此,即使将时间点截断为小时也可能取决于时区和一年中的时间。

因此,您无法在

date_trunc('hour', event_at)
上创建索引。表达方式不是
IMMUTABLE
。出于类似的原因,转换为
time
timestamp
也只是
STABLE
。因此涉及
date_trunc('hour', (event_at::time))
的索引也是不可能的。

解决方法包括:

  • 切换到
    timestamp without time zone
    。 (仅当确实合适时,大多数情况下您都想要
    timestamp with time zone
    !)
  • 添加一个带有截断时间戳的生成列,并在索引中使用它。看:
  • 创建一个(假的)不可变包装函数并在索引和查询中使用它。确保它只在结果事实上、不可变的地方使用,否则会发生不好的事情。例子:
    • 时间戳上的索引:索引表达式中的函数必须标记为 IMMUTABLE
也就是说,我怀疑你的索引会有多大帮助,甚至一开始就不会被使用。

如果您希望进行仅索引扫描,请将

success

 移至索引的 
INCLUDE
 部分。 (它不用于过滤或排序。)您还必须包含原始列
event_at
以使仅索引扫描成为可能(查询规划器的弱点):

CREATE INDEX metric_events_composite_index ON metric_events (metric_id, code, test, fake_immutable_date_trunc('hour', event_at)) INCLUDE (success, event_at);
现在索引太大了,我不确定它会有帮助。而且我不确定连接是否允许开始使用索引。

参见:

  • 使用 INCLUDE 相对于在 INDEX 中添加列来覆盖索引的优点
  • PostgreSQL 中的覆盖索引有助于 JOIN 列吗?
© www.soinside.com 2019 - 2024. All rights reserved.