Sql 按顺序将30毫秒查询转换为7120毫秒查询。已知的性能问题?

Sql 按顺序将30毫秒查询转换为7120毫秒查询。已知的性能问题?,sql,ruby-on-rails,ruby-on-rails-3,postgresql,postgresql-performance,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

我有一个包含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"

在我看来,你可以在这里有更好的索引;您正在根据
deleted_at
字段进行筛选,然后在
guest
字段上进行排序,但这些字段不在公共索引中。暂时忽略您的另一个
WHERE
子句,您似乎正在导致引擎挖掘所有记录,或者只是单独检查每个记录的
guest
值;我看不出你的
guest
索引有什么帮助


如果将
guest
字段与
deleted\u at
字段(后者是第一个)一起包含在索引中,您可能会从中获得一些好处。

乍一看,您的问题是需要全面评估where子句,以便获得所有(而不仅仅是前25行)您需要在之后订购的内容。。。尝试添加一列包含
子字符串(lower(fname),1,1))
现在让我们命名它
s
,并在
deleted\u at,s
上添加一个索引,或者如果这是唯一的值,您将在
(deleted为null),(s='s')
上建立索引

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


为了使它暂时更快,您可以将
子字符串(lower(fname),1,1))
重写为
lower(substring(fname,1,1))
,或者如果postgresql有这种语法
lower(fname[1])

,如果列中几乎没有不同的值,那么该列上的索引就没有多大价值。布尔列就是这种情况

我将测试在
子字符串(lower(fname),1,1)

并在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;

首先,由于PostgreSQL 9.1,您可以使用它来简化表达式:

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=FALSE
相同,只是不计算每行的新值。
接下来,创建以下内容:

或者,更好的是,(如果您没有需要不同顺序的更重要的查询),然后
分析

CLUSTER users using users_multi_idx;

这将比你以前尝试过的任何方法都要快。因为现在,查询按顺序读取索引中的行,并且表已按相同的顺序进行了物理重写,结果只有很少的页面点击…

您能否在查询上发布解释或更好的解释分析,以查看查询计划是什么?更新。。。这就是您需要的吗?
User(id、fname、lname、deleted\u at、guest)
请给我们真正的表定义,包括键、约束和索引。布尔字段上的索引不做任何IIRC。在order子句中使用fname是否更快?您是否在某处提到了PostgreSQL的版本?谢谢Erwin,我将尝试一下。我需要弄清楚如何将多列索引添加到rails@ColdTree:您可以只运行原始SQL。这是一次性手术,太不可思议了。非常感谢。欧文,快速跟进问题。有没有一种方法可以使这项工作也能够查询所有非a-Z字符。我一直在使用User.where(“fname~E'^[^a-zA-Z].*),现在请注意,使用“lower(left(fname,1))~E'^[^a-zA-Z].*”时出现错误。感谢该查询确实返回了非a-Z,并带有以下内容“lower(left(fname,1))~E'^[^a-zA-Z].*”“然而,演出失败了。思想?谢谢如果您将来宾从部分索引中排除,那么您将前进一步,并且该索引不再可用于查询。他们需要被包括在内,只是排序。@ErwinBrandstetter好的,你是对的。如果你介意的话,我上传了一个测试。谢谢,我有类似的现实生活(测试)数据库,我可以使用。但可能对其他人有用。
CREATE INDEX users_multi_idx
ON users (lower(left(fname, 1)), guest DESC NULLS LAST, fname)
WHERE deleted_at IS NULL;
ANALYZE users;
CLUSTER users using users_multi_idx;