概要:単純なデータベーススキーマがありますが、数十、数千のレコードでも、基本的なクエリのパフォーマンスは既に問題になっています。
データベース:PostgreSQL 9.6
簡略化されたスキーマ:
CREATE TABLE article (
id bigint PRIMARY KEY,
title text NOT NULL,
score int NOT NULL
);
CREATE TABLE tag (
id bigint PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE article_tag (
article_id bigint NOT NULL REFERENCES article (id),
tag_id bigint NOT NULL REFERENCES tag (id),
PRIMARY KEY (article_id, tag_id)
);
CREATE INDEX ON article (score);
生産データ情報:
すべてのテーブルは読み取り/書き込み可能です。書き込み量が少なく、数分ごとに新しいレコードのみ。
おおよそのレコード数:
記事ごとに平均5つのタグ。
質問:すべての記事レコードのタグの配列を含むビューarticle_tags
を作成します。article.score
で順序付けでき、追加のフィルタリングの有無にかかわらずページ番号を付けられます。
初めての試みで、クエリの実行に350ミリ秒かかり、インデックスを使用していないことに驚いた。その後の試行で、それを5ミリ秒まで下げることができましたが、何が起こっているのか理解できません。これらすべてのクエリに同じ時間がかかると思います。 ここで欠けている重要な概念は何ですか?
試行(SQLフィドル):
paginationの場合、LIMIT
(およびOFFSET
)は単純ですが、通常、大きなテーブルには非効率的なツールです。 _LIMIT 10
_を使用したテストでは、氷山の一角のみが表示されます。どのクエリを選択しても、パフォーマンスはOFFSET
とともに低下します。
同時書き込みアクセスがないか、ほとんどない場合、優れたソリューションは、行番号とそれにインデックスを追加した_MATERIALIZED VIEW
_です。そして、すべてのクエリは行番号で行を選択します。
同時書き込み負荷では、そのようなMVはすぐに古くなります(ただし、MV CONCURRENTLY
をN分ごとに更新するなどの妥協案は許容される場合があります)。LIMIT
/OFFSET
は、「次のページ」がそこに移動するターゲットであるため、まったく正しく機能しません。また、LIMIT
/OFFSET
はそれに対応できません。最良の手法は、非公開の情報に依存します。
関連:
インデックスは一般的に見栄えがします。しかし、あなたのコメントは、テーブルtag
に多くの行があることを示しています。通常、tag
のようなテーブルへの書き込み負荷はほとんどなく、インデックスのみのサポートに最適です。したがって、複数列(「カバリング」)インデックスを追加します。
_CREATE INDEX ON tag(id, name);
_
関連:
実際にそれ以上ページが必要ない場合(厳密には「ページング」ではありません)、該当する行をarticle
beforeから削減する任意のクエリスタイルが適切です。関連するテーブルから詳細を取得する(高額)。 「限定サブクエリ」(3.)と「ラテラル結合」(4.)のソリューションは優れています。しかし、あなたはもっとうまくやることができます:
ARRAY
バリアントにはLATERAL
コンストラクタを使用します。
_SELECT a.id, a.title, a.score, tags.names
FROM article a
LEFT JOIN LATERAL (
SELECT ARRAY (
SELECT t.name
FROM article_tag a_t
JOIN tag t ON t.id = a_t.tag_id
WHERE a_t.article_id = a.id
-- ORDER BY t.id -- optionally sort array elements
)
) AS tags(names) ON true
ORDER BY a.score DESC
LIMIT 10;
_
LATERAL
サブクエリは、single_article_id
_のタグを一度にまとめるため、_GROUP BY article_id
_は冗長であり、結合条件_ON tags.article_id = article.id
_、および基本的なARRAY
コンストラクターは、残りの単純なケースではarray_agg(tag.name)
よりも安価です。
または、低く相関サブクエリを使用しますが、通常はさらに高速ですが、
_SELECT a.id, a.title, a.score
, ARRAY (
SELECT t.name
FROM article_tag a_t
JOIN tag t ON t.id = a_t.tag_id
WHERE a_t.article_id = a.id
-- ORDER BY t.id -- optionally sort array elements
) AS names
FROM article a
ORDER BY a.score DESC
LIMIT 10;
_