为等于和时间戳范围查询创建索引

时间:2014-09-25 11:28:12

标签: sql postgresql indexing date-range

我有一个像这样定义的表:

CREATE TABLE sessions (
    session_id BIGSERIAL PRIMARY KEY,
    session_start TIMESTAMP NOT NULL,
    session_end TIMESTAMP NOT NULL,
    site_id BIGINT NOT NULL,
    photo_id BIGINT NOT NULL,
    uuid TEXT NOT NULL
)

CREATE INDEX sessions_lookup ON sessions (
    site_id, photo_id, uuid, session_start, session_end
)

我需要针对它运行此类型的查询:

SELECT session_id
FROM sessions
WHERE site_id = %s
AND photo_id = %s
AND uuid = %s
AND %s <@ tsrange(
            session_start - interval '3 hours',
            session_end + interval '3 hours'
          )

范围查询部分有不同的变体(分别检查session_start和session_end,使用OVERLAPS等等,但据我测试,它们都做同样的事情。)

使用EXPLAIN ANALYZE我得到以下结果(使用实际值而不是占位符):

 Index Scan using sessions_lookup on sessions  (cost=0.00..8.30 rows=1 width=8) (actual time=0.062..0.063 rows=1 loops=1)
   Index Cond: ((site_id = 10113150) AND (photo_id = 10240980) AND (uuid = '042d6f26-e298-0140-a4cc-7bfd0f9ccd27'::text))
   Filter: (((session_start - '03:00:00'::interval) <= '2014-09-05 09:45:38'::timestamp without time zone) AND ((session_end + '03:00:00'::interval) >= '2014-09-05 09:45:38'::timestamp without time zone))
 Total runtime: 1.384 ms
(4 rows)

后续运行提供~0.080ms查询运行时。

理想情况下,我想将索引用作时间戳查找的一部分,但它似乎完全被忽略(在索引中有或没有它的结果相同)。我是否需要改变字段的顺序或者我做错了什么(我需要不同类型的索引)吗?

这是在具有45k记录的表格上进行测试,但如果需要,我可以创建更大的样本集。

1 个答案:

答案 0 :(得分:1)

初步想法:OVERLAPS operator的工作方式与重叠(&&range operator相似。包含(<@和变体)运算符稍有不同,但如果要测试单个时间点是否在(日期)时间范围内,它们的工作方式类似。

您的查询背后的原因无法使用您的索引,即您没有直接在查询中测试您的列。您测试了一个范围表达式,它是使用您的列创建的。在这些情况下,您通常可以设置expression based index,但在您的情况下,这也无济于事(已由@CraigRinger's comment提及)。

如果我们可以忘记一些范围和重叠,这里是你的WHERE子句:

(session_start - interval '3 hours' <= '2014-09-05 09:45:38') AND
(session_end + interval '3 hours' >= '2014-09-05 09:45:38')

让我们做一些数学运算:

(session_start <= '2014-09-05 09:45:38'::timestamp + interval '3 hours') AND
(session_end >= '2014-09-05 09:45:38'::timestamp - interval '3 hours')

现在将使用您的索引。

带参数:

SELECT session_id
FROM sessions
WHERE site_id = %s
AND photo_id = %s
AND uuid = %s
AND (session_start <= %s::timestamp + interval '3 hours')
AND (session_end >= %s::timestamp - interval '3 hours')

SQLFiddle