PostgreSQL 10.5を使用します。ユーザーがさまざまな結果の間を行き来できるページネーションシステムを作成しようとしています。
OFFSET
を使用しないようにするために、前のページの最後の行のid
をp
(prevId)というパラメーターで渡します。次に、id
がp
パラメーターで渡された数よりも大きい最初の3つの行を選択します。 ( この記事 で説明されています)
たとえば、前のページの最後の行のid
が5だった場合、id
が5より大きい最初の3行を選択します。
SELECT
id,
firstname,
lastname
FROM
people
WHERE
firstname = 'John'
AND id > 5
ORDER BY
ID ASC
LIMIT
3;
これはうまく機能し、タイミングもそれほど悪くありません:
Limit (cost=0.00..3.37 rows=3 width=17) (actual time=0.046..0.117 rows=3 loops=1)
-> Seq Scan on people (cost=0.00..4494.15 rows=4000 width=17) (actual time=0.044..0.114 rows=3 loops=1)
Filter: ((id > 5) AND (firstname = 'John'::text))
Rows Removed by Filter: 384
Planning time: 0.148 ms
Execution time: 0.147 ms
一方、ユーザーが前のページに戻りたい場合は、状況が少し異なります。
最初に、最初の行にid
を渡してから、その前にマイナス記号を付けて、id
が(正の)p
パラメーターより小さい行を選択する必要があることを示します。つまり、最初の行のid
が6の場合、p
パラメータは-6
。同様に、私のクエリは次のようになります。
SELECT
*
FROM
(
SELECT
id,
firstname,
lastname
FROM
people
WHERE
firstname = 'John'
AND id < 6
ORDER BY
id DESC
LIMIT
3
) as d
ORDER BY
id ASC;
上記のクエリでは、最初にid
が6未満の最後の3行を選択し、最初に説明した最初のクエリと同じ方法で表示するためにそれらを逆にします。
これは正常に機能しますが、データベースはほとんどすべての行を通過するため、パフォーマンスが低下しています。
Sort (cost=4252.75..4252.76 rows=1 width=17) (actual time=194.464..194.464 rows=0 loops=1)
Sort Key: people.id
Sort Method: quicksort Memory: 25kB
-> Limit (cost=4252.73..4252.73 rows=1 width=17) (actual time=194.460..194.460 rows=0 loops=1)
-> Sort (cost=4252.73..4252.73 rows=1 width=17) (actual time=194.459..194.459 rows=0 loops=1)
Sort Key: people.id DESC
Sort Method: quicksort Memory: 25kB
-> Gather (cost=1000.00..4252.72 rows=1 width=17) (actual time=194.448..212.010 rows=0 loops=1)
Workers Planned: 1
Workers Launched: 1
-> Parallel Seq Scan on people (cost=0.00..3252.62 rows=1 width=17) (actual time=18.132..18.132 rows=0 loops=2)
Filter: ((id < 13) AND (firstname = 'John'::text))
Rows Removed by Filter: 100505
Planning time: 0.116 ms
Execution time: 212.057 ms
これが言われて、私はあなたがここまで読んで時間を割いてくれたことに感謝します、そして私の質問はどのようにページネーションをより効率的にすることができますか?
パフォーマンスの鍵は、次の形式の一致するマルチカラムインデックスです。
CREATE UNIQUE INDEX ON people (firstname, id);
UNIQUE
、それがないとソート順があいまいになる可能性があるため、ピアから任意の結果を得ることができます。
UNIQUE
またはPRIMARY KEY
制約も機能します。
最初の列は例のように等しいかどうか確認されます(またはクエリと同じ方向に並べ替えられます)が、このインデックスはページングupおよびdownに適していますが、少しですページングupに適しています。
インデックスを配置すると(およびテーブルでANALYZE
を実行した後)、シーケンシャルスキャンは表示されなくなります(テーブルがsmallでない限り)。データベースは「ほとんどすべての行を通過」しなくなりました。
リンクしているすばらしい Markus Winandによるプレゼンテーション を読んでください。
複数のfirstname
にわたるページングが必要な場合は、ROW値を使用します。ページングの例:
SELECT *
FROM (
SELECT id, firstname, lastname
FROM people
WHERE (firstname, id) < ('John', 6) -- ROW values
ORDER BY firstname DESC, id DESC
LIMIT 3
) d
ORDER BY firstname, id;
関連:
IfSELECT
リストは、例のようにlastname
のみを追加します。その列をインデックスに追加しようとする場合があります。 インデックスのみのスキャン を取得するには:
CREATE UNIQUE INDEX ON people (firstname, id, lastname);
この順序でインデックス式。
今後の Postgres 11では、INCLUDE
カラム 。これにより、インデックスサイズが小さくなり、パフォーマンスが向上し、より多くの状況に適用できます。お気に入り:
CREATE UNIQUE INDEX ON people (firstname, id) INCLUDE (lastname);