私はかなり一般的な (少なくとも私が思うに) データベース構造を得ました: ニュース ( News(id, source_id)
) があり、各ニュースにはソース ( Source(id, url)
) があります。ソースは、Topic(id, title)
経由でトピック ( )に集約されますTopicSource(source_id, topic_id)
。さらに、User(id, name)
経由でニュースを既読としてマークできるユーザー ( ) がいますNewsRead(news_id, user_id)
。物事を明確にするための図を次に示します。
特定のユーザーのトピックの未読ニュースをカウントしたい。問題はNews
、テーブルが大きいことです (10^6 - 10^7 行)。幸いなことに、正確なカウントを知る必要はありません。このしきい値をカウント値として返すしきい値の後でカウントを停止してもかまいません。
1 つのトピックに対するこの回答に続いて、次のクエリを思いつきました。
SELECT t.topic_id, count(1) as unread_count
FROM (
SELECT 1, topic_id
FROM news n
JOIN topic_source t ON n.source_id = t.source_id
-- join news_read to filter already read news
LEFT JOIN news_read r
ON (n.id = r.news_id AND r.user_id = 1)
WHERE t.topic_id = 3 AND r.user_id IS NULL
LIMIT 10 -- Threshold
) t GROUP BY t.topic_id;
(クエリ プラン 1 )。このクエリは、テスト データベースで約 50 ミリ秒かかりますが、これは許容範囲です。
ここで、複数のトピックの未読数を選択したいと考えています。私はそのように選択しようとしました:
SELECT
t.topic_id,
(SELECT count(1)
FROM (SELECT 1 FROM news n
JOIN topic_source tt ON n.source_id = tt.source_id
LEFT JOIN news_read r
ON (n.id = r.news_id AND r.user_id = 1)
WHERE tt.topic_id = t.topic_id AND r.user_id IS NULL
LIMIT 10 -- Threshold
) t) AS unread_count
FROM topic_source t WHERE t.topic_id IN (1, 2) GROUP BY t.topic_id;
(クエリ プラン 2 )。しかし、理由は不明ですが、テストデータでは約 1.5 秒かかりますが、個々のクエリの合計は約 0.2 ~ 0.3 秒かかります。
ここで明らかに何かが欠けています。2番目のクエリに間違いはありますか? 未読のニュースの数を選択するより良い (より速い) 方法はありますか?
追加情報:
- これは、DB構造とクエリのフィドルです。
- 私はSQLAlchemyでPostgresSQL 10を使用しています(ただし、生のSQLは今のところ問題ありません)。
テーブルサイズ:
News - 10^6 - 10^7
User - 10^3
Source - 10^4
Topic - 10^3
TopicSource - 10^5
NewsRead - 10^6
UPD:クエリプランは、2番目のクエリを台無しにしたことを明確に示しています。どんな手がかりも大歓迎です。
UPD2:このクエリを横結合で試しました。これは、それぞれに対して最初の (最速の) クエリを実行するだけのはずですtopic_id
:
SELECT
id,
count(*)
FROM topic t
LEFT JOIN LATERAL (
SELECT ts.topic_id
FROM news n
LEFT JOIN news_read r
ON (n.id = r.news_id AND r.user_id = 1)
JOIN topic_source ts ON n.source_id = ts.source_id
WHERE ts.topic_id = t.id AND r.user_id IS NULL
LIMIT 10
) p ON TRUE
WHERE t.id IN (4, 10, 12, 16)
GROUP BY t.id;
(クエリ プラン 3 )。しかし、Pg プランナーはこれについて異なる意見を持っているようです。インデックス スキャンとマージ結合の代わりに、非常に遅い seq スキャンとハッシュ結合を実行します。