我有一个大约 10k 行的普通表,通常按名为“名称”的列排序。所以,我在这个列上添加了一个索引。现在选择它很快:
EXPLAIN ANALYZE SELECT * FROM crm_venue ORDER BY name ASC LIMIT 10;
...query plan...
Limit (cost=0.00..1.22 rows=10 width=154) (actual time=0.029..0.065 rows=10 loops=1)
-> Index Scan using crm_venue_name on crm_venue (cost=0.00..1317.73 rows=10768 width=154) (actual time=0.026..0.050 rows=10 loops=1)
Total runtime: 0.130 ms
如果我将 LIMIT
增加到 60(这大致是我在应用程序中使用的值),则总运行时间不会进一步增加。
因为我在这个表上使用了“逻辑删除模式”,所以我只考虑 delete_date NULL
的条目。所以这是我做出的一个常见选择:
SELECT * FROM crm_venue WHERE delete_date IS NULL ORDER BY name ASC LIMIT 10;
为了使该查询也变得快速,我在 name
列上放置了一个索引,具有如下约束:
CREATE INDEX name_delete_date_null ON crm_venue (name) WHERE delete_date IS NULL;
现在使用逻辑删除约束可以快速进行排序:
EXPLAIN ANALYZE SELECT * FROM crm_venue WHERE delete_date IS NULL ORDER BY name ASC LIMIT 10;
Limit (cost=0.00..84.93 rows=10 width=154) (actual time=0.020..0.039 rows=10 loops=1)
-> Index Scan using name_delete_date_null on crm_venue (cost=0.00..458.62 rows=54 width=154) (actual time=0.018..0.033 rows=10 loops=1)
Total runtime: 0.076 ms
太棒了!但这是我让自己陷入麻烦。应用程序很少调用前 10 行。因此,让我们选择更多行:
EXPLAIN ANALYZE SELECT * FROM crm_venue WHERE delete_date IS NULL ORDER BY name ASC LIMIT 20;
Limit (cost=135.81..135.86 rows=20 width=154) (actual time=18.171..18.189 rows=20 loops=1)
-> Sort (cost=135.81..135.94 rows=54 width=154) (actual time=18.168..18.173 rows=20 loops=1)
Sort Key: name
Sort Method: top-N heapsort Memory: 21kB
-> Bitmap Heap Scan on crm_venue (cost=4.67..134.37 rows=54 width=154) (actual time=2.355..8.126 rows=10768 loops=1)
Recheck Cond: (delete_date IS NULL)
-> Bitmap Index Scan on crm_venue_delete_date_null_idx (cost=0.00..4.66 rows=54 width=0) (actual time=2.270..2.270 rows=10768 loops=1)
Index Cond: (delete_date IS NULL)
Total runtime: 18.278 ms
如您所见,它从 0.1 毫秒变为 18 毫秒!
很明显,在某个点上,排序不能再使用索引来运行排序。我注意到,当我将 LIMIT
数字从 20 增加到更高数字时,它总是需要大约 20-25 毫秒。
我做错了吗,或者这是 PostgreSQL 的限制?为此类查询设置索引的最佳方法是什么?
最佳答案
我的猜测是,从逻辑上讲,索引由指向一组数据页上的一组行的指针组成。如果您获取已知仅具有“已删除”记录的页面,则在获取页面后无需重新检查该页面即可仅获取已删除的记录。
因此,当您执行 LIMIT 10 并按名称排序时,从索引返回的前 10 个都在仅由已删除记录组成的数据页(或多个页面)上。因为它知道这些页面是同类的,所以一旦从磁盘中获取它们就不必重新检查它们。一旦您增加到 LIMIT 20,前 20 个中至少有一个在包含未删除记录的混合页面上。这将迫使执行程序重新检查每条记录,因为它无法从磁盘或缓存中以小于 1 页的增量获取数据页。
作为实验,如果您可以创建一个索引(delete_date,名称)并发出命令 CLUSTER crm_venue ON,其中索引是您的新索引。这应该按照 delete_date 然后 name 的排序顺序重建表。为确保万无一失,您随后应该发出 REINDEX TABLE crm_venue。现在再次尝试查询。由于所有 NOT NULL 都将聚集在磁盘上,因此使用较大的 LIMIT 值可能会更快。
当然,这只是即兴理论,所以YMMV ...
关于sql - 使用 PostgreSQL 中的约束索引用于 ORDER BY 的列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3787731/