3

我有一个包含 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 需要7120 毫秒才能返回 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

4 回答 4

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 LAST结果与 相同guest = FALSE,只是没有为每一行计算一个新值。
接下来,创建这个多列部分索引

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;

它会比你以前尝试过的任何东西都要快得多。因为现在,查询从索引中顺序读取行,并且表已经以相同的顺序物理重写,导致只有很少的页面命中......

于 2012-10-15T23:57:22.957 回答
2

在我看来,您可以在这里进行更好的索引;您正在根据deleted_at字段进行过滤,然后对guest字段进行排序,但这些字段不在公共索引中。暂时忽略您的其他WHERE条款,您似乎正在使引擎挖掘所有记录,或者只是单独检查每条记录的guest价值;我看不出你的索引有guest什么帮助。

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

于 2012-10-15T23:24:12.053 回答
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;
于 2012-10-15T23:41:19.620 回答
0

乍一看,您的问题是需要完全评估 where 子句,以便获得所有(不仅仅是前 25 行)您需要在之后排序...尝试添加一个包含substring(lower(fname), 1, 1))让我们暂时命名它的列s并添加一个index on deleted_at, s,或者如果这是这些值,您将在其中使用 index on 制定此值(deleted is null), (s = 's')

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

为了让它暂时更快,你可以重写substring(lower(fname), 1, 1))或者lower(substring(fname, 1, 1))如果 postgresql 有这个语法lower(fname[1]))

于 2012-10-15T23:24:34.613 回答