使用 order by 时对大型 table 的慢查询

Slow query on a large table when using order by

我有一个 table 约 30M 元组。 table 看起来像:

id | first_name | last_name | email
-----------------------------------------
1  | foo        | bar       | foo@bar.com

first_name 和 last_name 也有一个索引(btree 索引)。

下面的查询用了大约 200 毫秒 return 结果:

SELECT  
  * 
FROM my_table 
 WHERE (first_name ILIKE 'a%') 
 LIMIT 10 OFFSET 0

但是下一个大约需要15秒(添加顺序)

SELECT  
  * 
FROM my_table 
 WHERE (first_name ILIKE 'a%') 
 ORDER BY last_name asc, first_name asc 
 LIMIT 10 OFFSET 0 

如何提高上次查询的性能?

我假设以下索引会加速 ORDER BY:

create index my_table_lname_fname on my_table (last_name, first_name)

CREATE INDEX my_table_idx1 ON my_table (last_name ASC NULLS LAST, first_name ASC NULLS LAST);

一些笔记,排名不分先后:

  • ASC NULLS LAST 是默认设置,但我想我会举例说明,以防您想玩弄排序。
    • 这是多列索引可以帮助您的罕见情况之一。 通常建议您创建多个单列索引并允许查询优化器选择要使用的索引而不是强迫它做出决定。
    • 我注意到其中的 ILIKE 性能不佳,可能是您真正的问题除非您正在使用 pg_trgm 包和 GIST 索引。

对于这个查询你有两个索引选择:

SELECT t.* 
FROM my_table 
WHERE first_name ILIKE 'a%'
ORDER BY last_name asc, first_name asc 
LIMIT 10 OFFSET 0 ;

一个用于 WHERE 子句。最好的索引是 my_table(first_name)。第二种可能是对 ORDER BYmy_table(last_name, first_name).

使用索引

哪个更好取决于您拥有的数据。如果以整体性能为目标,您可能想尝试两者,看看哪个效果更好。

最后,计算索引可能是最好的方法。对于您的情况,将查询写为:

SELECT t.* 
FROM my_table 
WHERE lower(substr(first_name, 1, 1)) = 'a'
ORDER BY last_name asc, first_name asc 
LIMIT 10 OFFSET 0 ;

那么,你要的索引就是mytable(lower(substr(first_name, 1, 1)), last_name, first_name)。此索引可用于 WHEREORDER BY,这对于此查询应该是最佳的。