製品でいっぱいのデータベースを想定しています。商品は1つのコレクションに属することができ、ユーザーによって作成されます。データベースの大まかなスケール:
ユーザーが持っている製品+コレクションの量、および各コレクション内の製品の量を取得しようとしています(この情報は、x日間すべて生成され、ElasticSearchで索引付けされるはずです)。
ユーザークエリについては、私は現在次のようなことをしています:
_ SELECT
users.*,
(SELECT
count(*)
FROM
products product
WHERE
product.user_id = user.id
) AS product_count,
(SELECT
count(*)
FROM
collections collection
WHERE
collection.user_id = user.id
) AS collection_count
FROM
users user
_
すべての* _idフィールドにインデックスが付けられます。 Explain(analyze、verbose)を使用(削除された機密情報):
_ Limit (cost=0.00..156500.97 rows=100 width=41) (actual time=0.064..28345.363 rows=100 loops=1)
Output: (...), ((SubPlan 1)), ((SubPlan 2))
-> Seq Scan on public.users user (cost=0.00..14549429167.11 rows=9296702 width=41) (actual time=0.064..28345.241 rows=100 loops=1)
Output: (...), (SubPlan 1), (SubPlan 2)
SubPlan 1
-> Aggregate (cost=1415.84..1415.85 rows=1 width=0) (actual time=261.101..261.102 rows=1 loops=100)
Output: count(*)
-> Bitmap Heap Scan on public.products product (cost=7.32..1414.95 rows=355 width=0) (actual time=0.282..260.767 rows=382 loops=100)
Output: (...)
Recheck Cond: (product.user_id = user.id)
Heap Blocks: exact=32882
-> Bitmap Index Scan on products_user_id_index (cost=0.00..7.23 rows=355 width=0) (actual time=0.165..0.165 rows=382 loops=100)
Index Cond: (product.user_id = user.id)
SubPlan 2
-> Aggregate (cost=149.13..149.14 rows=1 width=0) (actual time=22.333..22.333 rows=1 loops=100)
Output: count(*)
-> Index Only Scan using collections_user_id_index on public.collections collection (cost=0.43..149.02 rows=44 width=0) (actual time=0.610..22.300 rows=28 loops=100)
Output: collection.user_id
Index Cond: (collection.user_id = user.id)
Heap Fetches: 2850
Planning time: 0.214 ms
Execution time: 28345.508 ms
_
読み取りクエリのタイミングをとるとき:
数行以上を取得するとクエリ時間が非常に遅くなるので、これを少し高速化できるかどうか疑問に思っています。
DBマシンを強化する場合、CPUを追加すると役立つでしょうか? AFAIK postgresは複数のコアでクエリを実行しないため、それがどれだけ役立つかわかりません。
(これも少し関連していますが、コレクションのcount()
がインデックスのみのスキャンを使用するのに、製品はビットマップヒープスキャンを使用するのはなぜですか?)
すべてまたはほとんどのユーザーの数値を計算している間、much相関サブクエリの代わりに、結合する前にプレーンサブクエリを使用してユーザーごとのカウントを集計する方が効率的です。
SELECT u.*, p.product_count, c.collection_count
FROM users u
LEFT JOIN (
SELECT user_id AS id, count(*) AS product_count
FROM products
GROUP BY 1
) p USING (id)
LEFT JOIN (
SELECT user_id As id, count(*) AS collection_count
FROM collections
GROUP BY 1
) c USING (id);
EXPLAIN
出力に表示されるインデックスのみのスキャンとビットマップインデックスは、行の小さなサブセット(LIMIT 100
)。あなたのテストはこの点で誤解を招くものです。すべて(またはほとんど)のユーザーの数値を計算している間、インデックスは役に立ちません。順次スキャンはより高速になります。
Bitmap Heap Scan
ご覧のとおり、ビットマップインデックススキャンに必要な2番目の手順のみです。これは、100行だけの場合の驚きです。テーブルの統計が古くなっている、または100人のユーザーが関連する製品を多く持っているか、products
の行が高度にクラスター化されている(物理的に、つまり、1人のユーザーの複数の行が同じまたは少数のデータページに存在することを意味します)。 Postgresは、データページごとに複数の行が見つかると予想される場合にのみ、ビットマップインデックススキャンに切り替えます。これは、100ユーザーと52.000.000製品(rows=355
が期待され、rows=382
が見つかりました)。
関連: