如何优化 PostgreSQL 中 big table 的 BETWEEN 条件

How to optimize BETWEEN condition on big table in PostgreSQL

我有一个很大的 table(大约一千万行),我需要用 ? BETWEEN columnA AND columnB.

执行查询

使用 table 和示例数据创建数据库的脚本:

CREATE DATABASE test;
\c test
-- Create test table
CREATE TABLE test (id INT PRIMARY KEY, range_start NUMERIC(12, 0), range_end NUMERIC(12, 0));
-- Fill the table with sample data
INSERT INTO test (SELECT value, value, value FROM (SELECT generate_series(1, 10000000) AS value) source);
-- Query I want to be optimized
SELECT * FROM test WHERE 5000000 BETWEEN range_start AND range_end;

我想创建 INDEX 以便 PostgreSQL 可以快速执行 INDEX SCAN 而不是 SEQ SCAN。但是我最初的(也是明显的)尝试失败了:

CREATE INDEX test1 ON test (range_start, range_end);
CREATE INDEX test2 ON test (range_start DESC, range_end);
CREATE INDEX test3 ON test (range_end, range_start);

另请注意,查询中的数字专门选择在生成值的中间(否则 PostgreSQL 能够识别该值接近范围边界并执行一些优化)。

如有任何想法或想法,我们将不胜感激。


更新 1 根据 official documentation 看来 PostgreSQL 无法正确使用索引来处理多列不等式条件。我不确定为什么会有这样的限制,如果我能做些什么来显着加快查询速度。


更新 2 一种可能的方法是通过了解我拥有的最大范围来限制 INDEX SCAN,假设它是 100000:

SELECT * FROM test WHERE range_start BETWEEN 4900000 AND 5000000 AND range_end > 5000000;

您为什么不尝试使用要点索引的范围?

alter table test add  numr numrange;

update test set numr = numrange(range_start,range_end,'[]');

CREATE INDEX test_idx ON test USING gist (numr);

EXPLAIN ANALYZE  SELECT * FROM test WHERE 5000000.0  <@ numr;

Bitmap Heap Scan on public.test  (cost=2367.92..130112.36 rows=50000 width=48) (actual time=0.150..0.151 rows=1 loops=1)
  Output: id, range_start, range_end, numr
  Recheck Cond: (5000000.0 <@ test.numr)
  ->  Bitmap Index Scan on test_idx  (cost=0.00..2355.42 rows=50000 width=0) (actual time=0.142..0.142 rows=1 loops=1)
        Index Cond: (5000000.0 <@ test.numr)
Total runtime: 0.189 ms

再想一想,为什么 PostgreSQL 不能对双列不等条件使用多列索引就很明显了。但是我不明白的是为什么即使有 LIMIT 子句也有 SEQ SCAN(抱歉没有在我的问题中表达):

test=# EXPLAIN ANALYZE SELECT * FROM test WHERE 5000000 BETWEEN range_start AND range_end LIMIT 1;
                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..0.09 rows=1 width=16) (actual time=4743.035..4743.037 rows=1 loops=1)
   ->  Seq Scan on test  (cost=0.00..213685.51 rows=2499795 width=16) (actual time=4743.032..4743.032 rows=1 loops=1)
         Filter: ((5000000::numeric >= range_start) AND (5000000::numeric <= range_end))
 Total runtime: 4743.064 ms

然后我突然想到,PostgreSQL 不知道结果在 range_start=1 中的可能性比 range_start=4999999 小。这就是它从第一行开始扫描直到找到匹配行的原因。

解决方案可能是让 PostgreSQL 相信使用索引有一些好处:

test=# EXPLAIN ANALYZE SELECT * FROM test WHERE 5000000 BETWEEN range_start AND range_end ORDER BY range_start DESC LIMIT 1;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..1.53 rows=1 width=16) (actual time=0.102..0.103 rows=1 loops=1)
   ->  Index Scan Backward using test1 on test  (cost=0.00..3667714.71 rows=2403325 width=16) (actual time=0.099..0.099 rows=1 loops=1)
         Index Cond: ((5000000::numeric >= range_start) AND (5000000::numeric <= range_end))
 Total runtime: 0.125 ms

我会说相当大的性能提升:)。然而,这种提升只有在存在这样的范围时才会起作用。否则它会和 SEQ SCAN 一样慢。因此,将这种方法与我在对原始问题的第二次更新中概述的方法结合起来可能会很好。