【发布时间】:2019-10-15 20:30:16
【问题描述】:
我们有一个类似下面的结构:
create table company
(
id bigint not null,
tz text not null
);
create table company_data
(
company_id bigint not null,
ts_tz timestamp with time zone not null
);
表格已简化。
在这里摆弄示例数据:SQL Fiddle
每家公司都有固定的 TZ。因此,当我们需要从company_data 中提取一些信息时,我们使用类似于以下的查询:
select
cd.company_id,
cd.ts_tz at time zone c.tz
from company_data cd
join company c on c.id = cd.company_id;
我们还有一个获取公司tz的功能:
create or replace function tz_company(f_company_id bigint) returns text
language plpgsql
as
$$
declare
f_tz text;
begin
select c.tz from company c where c.id = f_company_id into f_tz;
return f_tz;
end;
$$;
另一个在应用 tz 的日期中转换 ts:
create or replace function tz_date(timestamp with time zone, text) returns date
language plpgsql
immutable strict
as
$$
begin
return ($1 at time zone $2) :: date;
end;
$$;
我们现在遇到的问题是company_data(和其他类似的表)是一个大且经常使用的表。该表中的大部分SELECTs 使用DATE 执行过滤。
例如:
select cd.company_id,
cd.ts_tz at time zone tz_company(cd.company_id)
from company_data cd
where tz_date(cd.ts_tz, tz_company(cd.company_id)) >= '2019-08-20'
and tz_date(cd.ts_tz, tz_company(cd.company_id)) <= '2019-08-22';
所以,为了加快查询速度,我们需要在company_data.ts_tz 列中添加一个索引。我们发现这样做的唯一方法是:
create index idx_company_data_ts_tz on company_data
(((company_data.ts_tz at time zone tz_company(company_data.company_id))::date));
为此,我们需要将tz_company 函数设为immutable。
出现了一些其他问题(和想法):
1 - 使用tz_date 函数的查询版本不使用索引。
不使用索引:
explain analyse
select cd.company_id,
cd.ts_tz at time zone tz_company(cd.company_id)
from company_data cd
where tz_date(cd.ts_tz, tz_company(cd.company_id)) >= '2019-08-20'
and tz_date(cd.ts_tz, tz_company(cd.company_id)) <= '2019-08-22';
使用索引:
explain analyse
select cd.company_id,
cd.ts_tz at time zone tz_company(cd.company_id)
from company_data cd
where (cd.ts_tz at time zone tz_company(cd.company_id))::date >= '2019-08-20'
and (cd.ts_tz at time zone tz_company(cd.company_id))::date <= '2019-08-22';
为什么会这样?
2 - 我们知道,理论上,tz_company 不应该是immutable,最多稳定。但是,公司 tz 是一个永远不应该改变的信息。是的,它可能会发生,但它是不可能的。三年来,我们从未改变过任何一家公司的tz。那么,tz_company 成为immutable 仍然是个问题吗?如果是,我们如何重写索引?请注意,单个SELECT 可能会带来多个公司的信息并混合不同的时区。
3 - 由于处理timestamptz 列中的索引的复杂性,我们考虑在每个具有ts_tz 的表中添加另一列。此新列将是已应用 tz 的日期。这是一个好方法吗?
此外,我们需要在转换之前应用 tz,因为每个客户(公司)只选择要过滤的日期,并且这些日期是区域设置感知的(tz感知的)。
编辑 1:
使用的查询仅用于演示。但是一个要求是客户端看到事件发生的时区中的时间戳,这是一个重要的要求。我们处理巴西的物流业务,巴西本身在全国有四个不同的时区。 控股公司可能拥有不同的公司,而且每家公司可能位于不同的时区。
因此,许多查询涉及不同时区的不同公司,并应用了一些日期过滤。今天,我们的后端返回所有准备显示的数据,并应用了时区,这很难改变。
我们想要实现的是一种处理那些 timestamptz 列的简单且高效的方式:应用按日期过滤(tz 感知)并使用索引来加速查询。
【问题讨论】:
-
"为什么会发生 [to use the index]?" - 因为只有当 exact 表达式出现在查询,并且您的索引表达式包含
::date演员表。 -
我会尝试使
tz_company成为一个稳定的函数并使用language SQL。理论上,优化器可能会内联它并将其优化为一个连接——它永远不会用 plpgsql 这样做。 -
另一个想法:尝试将比较的日期放入公司时区,然后与
ts_tz比较,而不是将ts_tz移出时区。也许这允许在ts_tz上使用普通索引。 -
还有一个想法,或者实际上更多的黑客:尝试添加
cd.ts_tz >= '2019-08-20' - '1 day' AND cd.ts_tz <= '2019-08-22' + '1 day' conditions. They should be able to use a normal index onts_tz`,作为更复杂的时区调整比较之前的预过滤器,同时不删除任何正确的匹配给你时区知识。 -
@Bergi 最后一条评论(可索引的预过滤器)肯定是赢家,但内联函数也有机会。这足以回答问题。
标签: postgresql