3

100万レコードのユーザーテーブルがあります。

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

postgres9.1dbに対して実行されている次のクエリがあります。

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は25行を返すのに7120msかかります。'ORDER BY guest = false、fname ASC'を削除すると、クエリにかかる時間はわずか31ミリ秒です。

私は次のインデックスを持っています:

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"

何か案は?ありがとうございました!

Explainで更新

"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

left()まず、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 DESC NULLS LASTguest = 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

一見したところ、問題は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]))

于 2012-10-15T23:24:34.613 に答える
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 に答える