2010-09-24 102 views
1

我有一个约10k行的普通表,它通常按名为'name'的列进行排序。所以,我在这个专栏上增加了一个索引。现在选择它的速度快:使用PostgreSQL中的约束对ORDER BY使用的索引进行索引

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的限制?为这种类型的查询设置索引的最佳方式是什么?

回答

0

随着您增加行数,索引基数发生变化。我不确定,但可能是因为它使用的表中有更多的行,它需要读取足够多的表格块,这些表格块加上索引块足以使索引不再有意义使用。这可能是计划者的错误计算。你的名字(被索引的字段)也不是限制索引范围的字段,这可能会对规划师数学造成严重破坏。

可以尝试的事情: 在构建统计信息时增加考虑的表的百分比,您的数据可能会出现倾斜,导致统计信息无法获取真实的代表性示例。

索引所有行,而不仅仅是NULL行,看哪个更好。你甚至可以在NOT NULL的地方尝试索引。

基于该字段上的索引的群集可减少所需的数据块并将其转换为范围扫描。

空值和索引不总是很好。尝试另一种方式:

alter table crm_venue add column char delete_flag; 
update crm_venue set delete flag='Y' where delete_date is not null; 
update crm_venue set delete flag='N' where delete_date is null; 
create index deleted_venue (delete_flag) where delete_flag = 'N'; 
SELECT * FROM crm_venue WHERE delete__flag='Y' ORDER BY name ASC LIMIT 20; 
1

我的猜测是,因为在逻辑上,索引是由指向一组数据页面上的一组行的指针组成的。如果您只抓取一个已知仅具有“已删除”记录的页面,那么一旦抓取该页面以仅抓取已删除的记录,就不必重新检查该页面。

因此,可能是因为当您执行限制10并按名称排序时,从索引返回的前10个数据都位于仅包含已删除记录的数据页面(或多个页面)上。既然它知道这些页面是同质的,那么一旦从磁盘中获取它们,就不必重新检查它们。一旦您增加到限制20,前20个中至少有一个在混合页面上,并且未删除记录。这会迫使执行者重新检查每条记录,因为它无法从磁盘或缓存以小于1页的增量获取数据页。

作为一个实验,如果您可以创建索引(delete_date,name)并发出命令CLUSTER crm_venue ON,其中索引是您的新索引。这应该按照delete_date和name的排序顺序重建表。为了超级确定,你应该发布一个REINDEX TABLE crm_venue。现在再次尝试查询。由于所有NOT NULL都将聚集在磁盘上,因此可以使用更大的LIMIT值更快地工作。

当然,这一切都是非现成的理论,所以YMMV ...

+0

非常感谢你的答案。不幸的是,使用布尔值而不是日期不是一个选项。将它作为可空日期是A)比bool更实用,B)现在应用程序承担这种变化已经太迟了。 – 2010-09-26 17:34:59

+0

我的歉意,我不是故意暗示你需要添加一个布尔值。我很习惯软删除“旗帜”的人,我毫不犹豫地写下了它。我将更新条目以读取“删除日期”而不是“delete_flag”,以便更清楚。 – 2010-09-27 14:25:33