sql - 使用 PostgreSQL 中的约束索引用于 ORDER BY 的列

标签 sql postgresql indexing

我有一个大约 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/

相关文章:

javascript - 在 sequelize js 中获取关系的值(value)

mysql - mySQL 中基于哈希索引的数据库引擎

tsql - 重建 SQL 索引 - 何时?

PostgreSQL:是否可以为 PRIMARY KEY 或 UNIQUE 提供自定义名称?

mysql - 通过在同一表中选择具有另一个条件的行来更新具有 where 条件的行

postgresql - 如何确定列中的值是否可以在 Postgres 中编码为拉丁文

java - 将大行写入文件

mysql - MYSQL 中的查询,其中列作为表名,另一列作为列作为表名的外键

mysql - 使用 SQL join 仅选择最新版本的数据

SQL 回退行?