Sql 按顺序将30毫秒查询转换为7120毫秒查询。已知的性能问题?
我有一个包含1m条记录的用户表: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
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;