3

私の問題はJOIN、同じテーブルで 2 回使用するとクエリが非常に遅くなることです。

特定のカテゴリからすべての製品を取得したいと考えています。しかし、製品は複数のカテゴリに属する​​可能性c.canonicalがあるため、URL ベースを提供する ( ) カテゴリも取得したいと考えています。したがって、 と の 2 つの追加JOINcategories AS cありcategories_products AS cp2ます。

元のクエリ

SELECT p.product_id
FROM products AS p
JOIN categories_products AS cp
    ON p.product_id = cp.product_id
JOIN product_variants AS pv
    ON pv.product_id = p.product_id
WHERE cp.category_id = 2
    AND p.status = 2
GROUP BY p.product_id
ORDER BY cp.product_sortorder ASC
LIMIT 0, 40

説明

| id | select_type | table |   type |          possible_keys |                    key | key_len |                     ref | rows |                                        extra |
|----|-------------|-------|--------|------------------------|------------------------|---------|-------------------------|------|----------------------------------------------|
|  1 |      SIMPLE |    cp |    ref | FK_categories_products | FK_categories_products |       4 |                   const | 1074 | Using where; Using temporary; Using filesort |
|  1 |      SIMPLE |     p | eq_ref |                PRIMARY |                PRIMARY |       4 | superlove.cp.product_id |    1 |                                    Using where |
|  1 |      SIMPLE |    pv |    ref |    FK_product_variants |    FK_product_variants |       4 |  superlove.p.product_id |    1 |                                    Using where |    

遅いクエリ

SELECT p.product_id, c.category_id
FROM products AS p
JOIN categories_products AS cp
    ON p.product_id = cp.product_id
JOIN categories_products AS cp2        // Extra line
    ON p.product_id = cp2.product_id   // Extra line
JOIN categories AS c                   // Extra line
    ON cp2.category_id = c.category_id // Extra line
JOIN product_variants AS pv
    ON pv.product_id = p.product_id
WHERE cp.category_id = 2
    AND p.status = 2
    AND c.canonical = 1                // Extra line
GROUP BY p.product_id
ORDER BY cp.product_sortorder ASC
LIMIT 0, 40

説明

| id | select_type | table |   type |          possible_keys |                    key | key_len |                      ref | rows |                                        extra |
|----|-------------|-------|--------|------------------------|------------------------|---------|--------------------------|------|----------------------------------------------|
|  1 |      SIMPLE |     c |    ALL |                PRIMARY |                 (null) |  (null) |                   (null) |  221 | Using where; Using temporary; Using filesort |
|  1 |      SIMPLE |   cp2 |    ref | FK_categories_products | FK_categories_products |       4 |  superlove.c.category_id |   33 |                                              |
|  1 |      SIMPLE |     p | eq_ref |                PRIMARY |                PRIMARY |       4 | superlove.cp2.product_id |    1 |                                  Using where |
|  1 |      SIMPLE |    pv |    ref |    FK_product_variants |    FK_product_variants |       4 |   superlove.p.product_id |    1 |                                  Using where |
|  1 |      SIMPLE |    cp |    ref | FK_categories_products | FK_categories_products |       4 |                    const | 1074 |                                  Using where |
4

1 に答える 1

1

MySQL オプティマイザーは、このクエリに問題があるようです。要求されたカテゴリに含まれる製品はかなり少ないという印象を受けますが、正規のカテゴリは多数存在する可能性があります。cp.category_id = 2ただし、オプティマイザは、 が よりも強い条件であることを明らかにできないc.canonical = 1ため、新しいクエリをcではなく で開始しcp、途中で多くの余分な行が発生します。

オプティマイザへのデータの提供

最初の試みは、オプティマイザーに必要なデータを提供することです。ANALYZE TABLEコマンドを使用して、キー配布に関する情報を収集できます。これが機能するには、適切なキーを配置する必要があります。したがって、おそらく にキーを追加する必要がありますcategories.canonical。次に、MySQL は、(私の理解が正しければ) その列には 2 つの異なる値しかなく、それぞれの値が何行あるのかさえ知ることができます。少し運が良ければc.canonical = 1、開始点として使用するのは適切ではないことがわかります。

参加順序の強制

それでも解決しない場合は、 を使用して強制的に注文することをお勧めしますSTRAIGHT_JOINcp特に、元の (そして高速な) クエリと同じように、最初のテーブルとして強制したい場合があります。それで問題が解決する場合は、その解決策に固執することができます。そうでない場合は、新しいEXPLAIN出力を提供する必要があります。そうすれば、そのアプローチがどこで失敗するかがわかります。

スキーマに関する考慮事項

考慮すべきもう 1 つの点: あなたの質問は、すべての製品に対して、それに関連付けられた正規のカテゴリが 1 つだけあることを意味します。しかし、データベース スキーマはその事実を反映していません。その事実を反映するようにスキーマを変更する方法を検討することをお勧めします。たとえば、canonical_category_idin productstableという名前の列を作成categories_productsし、非標準カテゴリのみに使用できます。このようなセットアップを使用する場合、次のような を使用して、製品をすべてのカテゴリ (正規および非正規の両方)に結合する を作成するVIEWことができます。UNION

CREATE VIEW products_all_categories AS
SELECT product_id, canonical_category_id AS category_id
FROM products
UNION ALL
SELECT product_id, category_id
FROM categories_products

categories_productsカテゴリが正規かどうかを気にしない場所ではなく、これを使用できます。テーブルの名前を変更し、categories_products代わりにビューに名前を付けて、既存のクエリが以前と同じように機能するようにすることもできます。productsこのクエリで使用されている 2 つの列にインデックスを追加する必要があります。おそらく、これらの列のいずれかの順序に 1 つずつ、2 つのインデックスでさえあります。

このセットアップ全体がアプリケーションで受け入れられるかどうかはわかりません。本当に意図した速度向上が得られるかどうかはわかりません。products.canonical最終的に、テーブル内の正規カテゴリへの参照に加えて、列などの冗長データを維持することを余儀なくされる場合がありcategories_productsます。設計の観点から冗長データが見苦しいことは知っていますが、パフォーマンスのために、長い計算を避けるために必要になる場合があります。少なくともマテリアライズド ビューをサポートしない RDBMS では。実際の経験はありませんが、おそらくトリガーを使用してデータの一貫性を保つことができます。

于 2013-10-23T17:00:54.263 に答える