web-dev-qa-db-ja.com

2つのテーブルのインデックスを必要とするデータベース設計

PostgreSQL(現在は9.6ですが、アップグレードは可能です)を使用して、私は現在、次のデータベースレイアウトで顧客が製品を注文でき、それ自体がカテゴリに分類されています(製品は複数のカテゴリにある場合があります)。

Orders
id -- PRIMARY KEY
customer_id -- FOREIGN KEY (Customer - id)
product_id -> FOREIGN KEY (Product - id)

Products
id -- PRIMARY KEY

Categories
id -- PRIMARY KEY

Product_Categories
product_id -- FOREIGN KEY (Product - id)
category_id -- FOREIGN KEY (Category - id)

データ量

現在、私はかなり大量の注文(〜3千万)と適切な数のカテゴリ(〜1K)と顧客(〜1万)を持っています。約3万の製品があり、カテゴリごとに平均3つの製品があります。製品はカテゴリから別のカテゴリに移動されることがあります(月に1回シャッフルとしましょう)

クエリの傾向

私の問題は、次のタイプのクエリを高速に実行させたいことです"カテゴリCにある製品の顧客のすべての注文を取得します"。それは次のようになります:

SELECT * FROM Orders 
JOIN Product_Categories ON Orders.product_id = Product_Categories.product_id
WHERE Orders.customer_id = X AND Product_Categories.category_id = Y

インデックス作成に関する考慮事項

私が考えることができる最良のインデックスは、Ordersのcustomer_idのインデックスであり、Product_Categories.product_idのセカンダリインデックスによってサポートされています。これは次の計画につながります(上で示した設計は実際のケースを非常に単純化しているため、実際の計画ではありません)。

 - Index Scan on Orders using index on customer_id ---> Returns ~10K Rows
 - 10K Joins done by Index Lookup on the product_id index of Product_Categories (MAIN TIME CONSUMER)
 - 9990 Rows Filtered Out.
 - 10 Rows Returned

(customer_id, category_id)にインデックスを付けたいのですが、これを行う方法を見つけることができませんでした。私が考えることができる最良の解決策は、列categories_id INTEGER[]を追加してから、次のいずれかを行うことです:

  1. categories_idおよびcustomer_idを使用し、リスト演算子に含めてGINインデックスを追加します。
  2. order_idに1000個の部分インデックスを作成します。

どちらの場合でも、categories_idcategoryproduct関連付けテーブルの更新と同期する必要がありますが、これは残念です。

ご質問

私の質問は:

  • 私は考えすぎですか? 「10kのフィル​​タリング」行は問題の悪いことではありませんか、そして私が考えることができる任意の解決策は問題を悪化させますか?
  • 何か不足していますか?データベーススキーマを変更せずに効率的にできますか?
  • データベーススキーマを変更する必要があるとすると、そのための最良の方法は何ですか。
3
Rémi Bonnet

インデックスon product_categories (category_id)があり、すでにon orders (customer_id)があるインデックスがある場合、このタイプのクエリは非常に高速です。各テーブルで非常に具体的なインデックススキャンを個別に実行し、結果をハッシュ結合できます。

https://explain.depesz.com/s/JEpZ

それが十分に速くない場合、またはインデックスが配置されている場合でもそのようなプランを使用することができない場合は、実際のクエリのように、より多くの情報を提供する必要があると思いますタイミングを含めた計画、および達成したい時間。

1
jjanes