PostgreSQL 日期范围未正确使用索引

Sha*_*das 7 postgresql indexing date-range b-tree-index gist-index

我有一个简单的表,其中有一个带有日期类型的 user_birthday 字段(可以是 NULL 值)

CREATE TABLE users
(
  user_id bigserial NOT NULL,
  user_email text NOT NULL,
  user_password text,
  user_first_name text NOT NULL,
  user_middle_name text,
  user_last_name text NOT NULL,
  user_birthday date,
  CONSTRAINT pk_users PRIMARY KEY (user_id)
)
Run Code Online (Sandbox Code Playgroud)

该字段上定义了一个索引(btree),其规则为 NOT user_birthday IS NULL。

CREATE INDEX ix_users_birthday
  ON users
  USING btree
  (user_birthday)
  WHERE NOT user_birthday IS NULL;
Run Code Online (Sandbox Code Playgroud)

为了跟进另一个想法,我添加了扩展btree_gist并创建了以下索引:

CREATE INDEX ix_users_birthday_gist
  ON glances.users
  USING gist
  (user_birthday)
  WHERE NOT user_birthday IS NULL;
Run Code Online (Sandbox Code Playgroud)

但它也没有影响,因为据我所知,它不用于范围检查。

PostgreSQL 版本为 9.3.4.0 (22) Postgres.app ,问题也存在于 9.3.3.0 (21) Postgres.app中

我对以下问题很感兴趣:

查询#1:

EXPLAIN ANALYZE SELECT *
FROM users
WHERE user_birthday <@ daterange('[1978-07-15,1983-03-01)')
Run Code Online (Sandbox Code Playgroud)

查询#2:

EXPLAIN ANALYZE SELECT *
FROM users
WHERE user_birthday BETWEEN '1978-07-15'::date AND '1983-03-01'::date
Run Code Online (Sandbox Code Playgroud)

乍一看,两者应该具有相同的执行计划,但由于某种原因,结果如下:

查询#1:

"Seq Scan on users  (cost=0.00..52314.25 rows=11101 width=241) (actual
time=0.014..478.983 rows=208886 loops=1)"
"  Filter: (user_birthday <@ '[1978-07-15,1983-03-01)'::daterange)"
"  Rows Removed by Filter: 901214"
"Total runtime: 489.584 ms"
Run Code Online (Sandbox Code Playgroud)

查询#2:

"Bitmap Heap Scan on users  (cost=4468.01..46060.53 rows=210301 width=241)
(actual time=57.104..489.785 rows=209019 loops=1)"
"  Recheck Cond: ((user_birthday >= '1978-07-15'::date) AND (user_birthday
<= '1983-03-01'::date))"
"  Rows Removed by Index Recheck: 611375"
"  ->  Bitmap Index Scan on ix_users_birthday  (cost=0.00..4415.44
rows=210301 width=0) (actual time=54.621..54.621 rows=209019 loops=1)"
"        Index Cond: ((user_birthday >= '1978-07-15'::date) AND
(user_birthday <= '1983-03-01'::date))"
"Total runtime: 500.983 ms"
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它<@ daterange没有利用现有索引,而它却利用了现有索引 BETWEEN

需要注意的是,此规则的实际用例是在更复杂的查询中,这不会导致重新检查条件和位图堆扫描。在应用程序复杂查询中,两种方法(有 120 万条记录)之间的差异很大:查询#1 为 415 毫秒,查询#2 为 84 毫秒。

这是日期范围的错误吗?难道我做错了什么?或者是否datarange <@按设计执行?

pgsql-bugs 邮件列表中也有一个讨论

Erw*_*ter 5

BETWEEN 包括上边框和下边框。你的情况

WHERE user_birthday BETWEEN '1978-07-15'::date AND '1983-03-01'::date
Run Code Online (Sandbox Code Playgroud)

火柴

WHERE user_birthday <@ daterange('[1978-07-15,1983-03-01]')
Run Code Online (Sandbox Code Playgroud)

我看到你提到了btree索引。为此,请使用简单的比较运算符。

详细的手册页显示哪些索引适合哪些操作员

范围类型运算符<@or可以与GiST索引@>一起使用
示例:
在 PostgreSQL 中执行此操作时间查询