web-dev-qa-db-ja.com

MySQLで特定のインデックスを使用するようにJOINを強制するにはどうすればよいですか?

JOINs 2テーブルというクエリがあります。 lineitemおよびpart

select
        sum(l_extendedprice* (1 - l_discount)) as revenue
from
        lineitem force index for join (l_pk),
        part
where
        (
                p_partkey = l_partkey
                and p_brand = 'Brand#12'
                and p_container in ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG')
                and l_quantity >= 1 and l_quantity <= 1 + 10
                and p_size between 1 and 5 
                and l_shipmode in ('AIR', 'AIR REG')
                and l_shipinstruct = 'DELIVER IN PERSON'
        )
        or
        (
                p_partkey = l_partkey
                and p_brand = 'Brand#23'
                and p_container in ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK')
                and l_quantity >= 10 and l_quantity <= 10 + 10
                and p_size between 1 and 10
                and l_shipmode in ('AIR', 'AIR REG')
                and l_shipinstruct = 'DELIVER IN PERSON'
        )
        or
        (
                p_partkey = l_partkey
                and p_brand = 'Brand#34'
                and p_container in ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG')
                and l_quantity >= 20 and l_quantity <= 20 + 10
                and p_size between 1 and 15
                and l_shipmode in ('AIR', 'AIR REG')
                and l_shipinstruct = 'DELIVER IN PERSON'
        );

Lineitemにインデックスがあり、クエリでこのインデックスを結合に使用したいp_partkey = l_partkey

create index l_pk on tpch.lineitem(l_partkey);

MySQL explainは以下を示します:

| id | select_type | table    | type   | possible_keys | key     | key_len | ref                     | rows    | Extra       |       
+----+-------------+----------+--------+---------------+---------+---------+-------------------------+---------+-------------+
|  1 | SIMPLE      | lineitem | ALL    | l_pk          | NULL    | NULL    | NULL                    | 5982534 | Using where | 
|  1 | SIMPLE      | part     | eq_ref | PRIMARY       | PRIMARY | 4       | tpch.lineitem.L_PARTKEY |       1 | Using where | 
+----+-------------+----------+--------+---------------+---------+---------+-------------------------+---------+-------------+

なぜインデックスl_pk 使用されていない?

2
Alfred Zhong

FORCE INDEXを使用している場合でも、クエリオプティマイザーが期待どおりに引き継ぎました。

lineitemテーブルのパスが、インデックスエントリ全体の5%を超えました。これが、クエリオプティマイザーがインデックスの使用を却下する原因になります。 Oracle、MSSQL、およびPostgreSQLは何も変わらないでしょう。

クエリをリファクタリングしようとする私の病気の試みは次のとおりです

select sum(l_extendedprice* (1 - l_discount)) as revenue
from
(
    select p_partkey l_partkey,'S' contsize from part
    where p_brand = 'Brand#12'
    and p_container in ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG')
    and p_size between 1 and 5 
    union
    select p_partkey,'M' contsize from part
    where p_brand = 'Brand#23'
    and p_container in ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK')
    and p_size between 1 and 10
    union
    select p_partkey,'L' contsize from part
    where p_brand = 'Brand#34'
    and p_container in ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG')
    and p_size between 1 and 15
) partkeys
left join listitem using (l_partkey)
where
    l_shipmode in ('AIR', 'AIR REG') and
    l_shipinstruct = 'DELIVER IN PERSON' and
    IF(contsize='S',IF(l_quantity >= 1 and l_quantity <= 1 + 10,1,0),
        IF(contsize='M',IF(l_quantity >= 10 and l_quantity <= 10 + 10,1,0),
            IF(contsize='L',IF(l_quantity >= 20 and l_quantity <= 20 + 10,1,0),0)
        )
    )
;

テーブルにインデックスを付ける必要があるかもしれません

ALTER TABLE parts ADD INDEX brand_container_size_ndx
(p_brand,p_container,p_size,p_partkey);
ALTER TABLE listitem ADD INDEX partkey_shipinstruct_shipmode_quantity_ndx
(l_partkey,l_shipinstruct,l_shipmode ,l_quantity);

試してみて、高速か、それとも機能するかをお知らせください。

1
RolandoMySQLDBA

インデックスl_pkは、テーブルの結合方法が原因で使用されていません。

インデックスを使用するには、そのインデックスで検索するものを用意する必要があります。

2つのテーブルを結合する場合、左側のテーブルに値があり、右側のテーブルの対応する行と一致させる必要があるため、右側のテーブルのインデックスを使用して一致する行を検索します。または、右のテーブルと左のテーブルの行を一致させる必要があるため、左のテーブルのインデックスを使用して一致する行を見つけます。

両方のテーブルでインデックスを使用して結合を実行するのではなく、どちらか一方を使用します。この場合、partの主キーが結合に使用されています。

それで、次の質問は「なぜ?」です。

これに答えるために、クエリがサーバーに要求する内容を検討します。

サーバーによって実行されるこれらの2つのプロセスのどちらでも、同じ結果が生成されます。

サーバーに要求しています...

  1. l_quantity、l_shipmode、およびl_shipinstruct ...に指定した値に基づいてlineitem内のすべての行を検索し、パーツのすべての一致する行に結合しますが、p_brand、p_container、およびp_sizeの期待値も持つ行のみに結合します...または
  2. p_brand、p_container、およびp_sizeに基づいて部分的に行を検索し、l_quantity、l_shipmode、l_shipinstructに一致する値があるlineitemの一致する行を特定します。

きみの possible_keysそれぞれに1つのインデックスのみが含まれています。これは、検索している列にインデックスがないことを示しています... p_brand、p_container、p_size、l_quantity、l_shipmode、l_shipinstruct ...

したがって、サーバーはいずれかのテーブルで全テーブルスキャンを実行する以外に選択肢はありません。これがlineitemを選択しているのは、これが最も費用対効果の高いルートであると結論付けたからです。

実際の問題は、インデックスを作成する必要がある列を検索していることです。

1