我有下表:
CREATE TABLE items (
id serial
timestamp bigint
CONSTRAINT id_pkey PRIMARY KEY (id),
);
此表仅以附加方式使用,因此timestamp
值随id
增加。我需要找到timestamp
最接近特定$value
的行。
查询1:这需要两次全表扫描。
SELECT id FROM
(
(
SELECT id, timestamp
FROM records
WHERE timestamp < $value
ORDER BY timestamp DESC
LIMIT 1
)
UNION ALL
(
SELECT id, timestamp
FROM items
WHERE timestamp >= $value
ORDER BY timestamp ASC
LIMIT 1
)
) AS tmp
ORDER BY abs($value - timestamp)
LIMIT 1
查询2:这个似乎应该更快,但由于某种原因,它不是
SELECT id
FROM items
WHERE scan.gpstimestamp >= $value
ORDER BY id ASC
LIMIT 1
问题3:我正在试验需要全表扫描的自定义聚合,但不需要对任何内容进行排序或加载任何索引。
create function closest_id_sfunc(
agg_state bigint[2],
id bigint,
timestamp bigint,
target_timestamp bigint
)
returns bigint[2]
immutable
language plpgsql
as $$
declare
difference bigint;
begin
difference := abs(timestamp - target_timestamp);
if agg_state is null or difference < agg_state[0] then
agg_state[0] = difference;
agg_state[1] = id;
end if;
return agg_state;
end;
$$;
create function closest_id_finalfunc(agg_state bigint[2])
returns bigint
immutable
strict
language plpgsql
as $$
begin
return agg_state[1];
end;
$$;
create aggregate closest_id (bigint, bigint, bigint)
(
stype = bigint[2],
sfunc = closest_id_sfunc,
finalfunc = closest_id_finalfunc
);
SELECT closest_id(id, timestamp, $value) as id FROM items
为什么查询2比查询1慢?
答案 0 :(得分:1)
您的第二个查询将无效,因为在提供的时间戳之前可能会有一行,它更接近提供的值。准确性不是这里唯一关注的问题:可能没有一行,根本不是提供的时间戳(同时存在较低的值)。
您的第一个查询看起来效率很高(当您在子查询中使用limit 1
时)。但是,是的,它需要两个表扫描,当你没有索引,但你无法解决。您需要索引以获得巨大的性能提升。然而,有一些技巧可以使用。
我最初的想法是,您可以通过使用条件来避免外部查询的排序成本:
(注意:我会使用ts
作为列名,因为timestamp
是一个关键字&amp;不应该用作列名,除非它被转义。)
with l as (
select id, ts
from items
where ts < $value
order by ts desc
limit 1
),
g as (
select id, ts
from items
where ts >= $value
order by ts asc
limit 1
)
select case
when abs($value - l.ts) < abs($value - g.ts)
then l.id
else coalesce(g.id, l.id)
end id
from l
full join g on true
然而,这只会在我的测试中造成微小的性能提升(看起来PostgreSQL对于仅排序两行非常聪明)。
您可以通过对PostgreSQL的某些几何类型使用直接“距离”计算来加快查询速度。注意:这些类型通常使用double precision
作为值,因此它们可能包含舍入错误。如果您的值是真正的unix时间戳(bigint
),则很可能不会出现问题。
以下是在point
上使用始终可用的<->
类型的距离运算符point(ts, 0)
的查询(因此第二个坐标始终为零):
select id
from items
order by point(ts, 0) <-> point($value, 0)
limit 1
在我的测试中,这需要原始查询(或CTE变体)的约70%。
您还可以使用cube
module's cube
类型&amp; <->
上的(欧几里得)距离算子cube(ts)
( 9.6 + 特征)(因此立方体将始终是一维点):
select id
from items
order by cube(ts) <-> cube($value)
limit 1
这与速度上的point
变体相当。当你使用索引时,它会有一些差异。
(注意:您可以使用create extension cube;
初始化模块。)
<强>索引强>
所以,有趣的部分:
您的原始查询(或CTE变体)可以使用以下(覆盖)索引:
create index idx_items_ts_id on items (ts, id)
有了这个,您的原始查询(和CTE变体)使用仅索引扫描,其成本约为同一查询的1.5%(没有索引)。
point
变体可以使用以下GiST索引:
(注意:btree_gist
模块是id
成为索引的一部分。您可以使用create extension btree_gist;
初始化模块。)
create index idx_items_point_gist on items using gist (point(ts, 0), id)
这样,point
变体的成本约为原始查询的1%(没有索引)。
cube
变体可以使用以下GiST索引:
(注意:这也需要btree_gist
模块。)
create index idx_items_cube_gist on items using gist (cube(ts), id)
同样,这仍然与point
变体相当。
结论(请参阅稍后编辑)
使用point
或cube
(后者需要9.6+),您可以获得最佳性能。此外,索引可以帮助你很多。
补充说明:
point
变体实际上有时更快(比cube
变体)cube
索引&amp;我不知道为什么cube
索引应该更小,因为它不包含不必要的零。但是,因为它们更普遍(N维),我可能不对此。我建议试试这两个&amp;衡量(指数大小和绩效)。 http://rextester.com/KNY52367(这些查询也在cube
,但不会运行,因为rextester现在使用9.5。)
另外,我也测试了一个自定义聚合解决方案(基本上是你的版本,但我使用language sql
函数加速了一点,但仍然),它比原始查询慢了~10倍。恕我直言,这根本不值得。 http://rextester.com/PLG94853
修改:注意,btree_gist
模块为基本类型(例如<->
)添加了对距离运算符bigint
的支持。
因此,即使是point
和cube
变体,此查询也会超越(稍微):
select id
from items
order by ts <-> $value
limit 1
此索引最适合上述查询:
create index idx_items_ts_gist on items using gist (ts, id)