Order BY将30ms查询转换为7120ms查询。已知的性能问题?

时间:2012-10-15 23:15:13

标签: sql ruby-on-rails ruby-on-rails-3 postgresql postgresql-performance

我有一个包含1m记录的用户表:

User (id, fname, lname, deleted_at, guest)

我有一个针对postgres 9.1 db运行的以下查询:

SELECT "users".* 
FROM "users" 
WHERE (users.deleted_at IS NULL) AND (SUBSTRING(lower(fname), 1, 1) = 's') 
ORDER BY guest = false, fname ASC 
LIMIT 25 OFFSET 0

使用pgAdmin 3,此SQL将 7120ms 返回25行。如果我删除'ORDER BY guest = false,fname ASC',则查询只需 31ms

我有以下索引:

add_index "users", ["fname"], :name => "index_users_on_fname"
add_index "users", ["guest", "fname"], :name => "index_users_on_guest_and_fname"
add_index "users", ["deleted_at"], :name => "index_users_on_deleted_at"
add_index "users", ["guest"], :name => "index_users_on_guest"

有什么想法吗?谢谢!

更新了解释

"Limit  (cost=43541.55..43541.62 rows=25 width=1612) (actual time=1276.777..1276.783 rows=25 loops=1)"
"  ->  Sort  (cost=43541.55..43558.82 rows=6905 width=1612) (actual time=1276.775..1276.777 rows=25 loops=1)"
"        Sort Key: ((NOT guest)), fname"
"        Sort Method: top-N heapsort  Memory: 37kB"
"        ->  Seq Scan on users  (cost=0.00..43346.70 rows=6905 width=1612) (actual time=5.143..1272.563 rows=475 loops=1)"
"              Filter: ((deleted_at IS NULL) AND pubic_profile_visible AND ((fname)::text ~~ 's%'::text))"
"Total runtime: 1276.967 ms"

4 个答案:

答案 0 :(得分:4)

首先,自PostgreSQL 9.1起,您可以使用left()来简化表达式:

substring(lower(fname), 1, 1)
lower(left(fname, 1)) -- equivalent, but simpler and faster

将施放前的第一个字符转换为小写字母的速度稍快一些 接下来,清理查询:

SELECT * 
FROM   users 
WHERE  deleted_at IS NULL
AND    lower(left(fname, 1)) = 's'
ORDER  BY guest DESC NULLS LAST, fname
LIMIT  25 OFFSET 0;

guest DESC NULLS LASTguest = FALSE的结果相同,只是没有计算每一行的新值。
接下来,创建此multi-column partial index

CREATE INDEX users_multi_idx
ON users (lower(left(fname, 1)), guest DESC NULLS LAST, fname)
WHERE deleted_at IS NULL;

运行

ANALYZE users;

或者更好,CLUSTER(如果您没有更重要的查询需要不同的订单) - 并且然后 ANALYZE

CLUSTER users using users_multi_idx;

它会比你之前尝试过的任何事情都快。因为现在,查询按顺序从索引中读取行,并且表已经以相同的顺序进行了物理重写,导致只有少量页面命中...

答案 1 :(得分:2)

对我来说,你可以站在这里更好的索引;您正在基于deleted_at字段进行过滤,然后在guest字段上进行排序,但这些字段不在公共索引中。暂时忽略您的其他WHERE子句,您似乎导致引擎挖掘所有记录,或者只是单独检查每条记录的guest值;我看不到你的guest索引如何帮助。

如果您在索引中包含guest字段以及deleted_at字段(后者是第一个),那么您可能会获得一些好处。

答案 2 :(得分:0)

乍一看,您的问题是需要完全评估where子句,以便在需要订购之后获取所有(不仅仅是25个第一行)...尝试添加包含substring(lower(fname), 1, 1))的列我们暂时将其命名为s,并在deleted_at, s上添加索引,或者如果这是您将在(deleted is null), (s = 's')上建立索引的唯一值。

您可以使用触发器使s列保持最新状态。

要暂时加快速度,您只需将substring(lower(fname), 1, 1))重写为lower(substring(fname, 1, 1)),或者如果postgresql具有此语法lower(fname[1]))

答案 3 :(得分:0)

如果列中的值很少,则该列上的索引值不大。布尔列就是这种情况。

我会测试在SUBSTRING(lower(fname), 1, 1)

上创建部分索引
CREATE INDEX users_substr_null_ix ON users (SUBSTRING(lower(fname), 1, 1))
WHERE users.deleted_at IS NULL;

并测试fname上的部分索引:

CREATE INDEX users_fname_not_guest_ix ON users (fname)
WHERE not guest;

甚至更好

CREATE INDEX users_substr_null__not_guest_ix ON users (SUBSTRING(lower(fname), 1, 1), fname)
WHERE users.deleted_at IS NULL and not guest;