7

私は、ローカル変数を使用した再帰クエリを使用して、単純な隣接リスト内のノードのツリーを取得することで (興味がなく) 遊んでいました。

INDEXこれまでの解決策は楽しいですが、MySQL がこのクエリを最適化するために使用を拒否するのはなぜだろうか (これが私の唯一の質問です) 。MySQL は ? を使用して最も近い子を検索できませんINDEXか?

なぜMySQLがそうでないのか興味があります。実行計画を使用FORCE INDEXしても変更されません。

これはこれまでのクエリ5で、親ノードの ID です。

SELECT 
  @last_id := id AS id,
  parent_id,
  name,
  @depth := IF(parent_id = 5, 1, @depth + 1) AS depth
FROM 
  tree FORCE INDEX (index_parent_id, PRIMARY, index_both),
  (SELECT @last_id := 5, @depth := -1) vars
WHERE id = 5 OR parent_id = @last_id OR parent_id = 5

SQLfiddle で実際の例を試す

FORCE INDEX (id)orFORCE INDEX (parent_id)またはFORCE INDEX (id, parent_id)...を指定しても動作は変わらないため、理由は小さなデータセットではないことに注意してください。

ドキュメントは言う:

また、USE INDEX (index_list) のように機能する FORCE INDEX を使用することもできますが、テーブル スキャンは非常に高価であると想定されます。つまり、テーブル スキャンは、特定のインデックスのいずれかを使用してテーブル内の行を検索する方法がない場合にのみ使用されます。

クエリが INDEX を使用できないようにする何かがあるに違いありませんが、それが何であるかわかりません。


免責事項: SQL で階層データを格納および取得するには、さまざまな方法があることを知っています。ネストされたセットモデルについて知っています。私は代替の実装を探していません。入れ子になったセットを探しているわけではありません。

また、クエリ自体がおかしく、間違った結果を生成することも知っています。

INDEXこの場合、MySQL が を使用しない理由を (詳細に) 理解したいだけです。

4

1 に答える 1

2

その理由は、 WHERE句でのOR条件の使用にあります。

説明のために、今度はid = 5条件のみを使用してクエリを再度実行し、get (EXPLAIN 出力)を試します。

+----+-------------+------------+--------+--------------------+---------+---------+-------+------+----------------+
| id | select_type | table      | type   | possible_keys      | key     | key_len | ref   | rows | Extra          |
+----+-------------+------------+--------+--------------------+---------+---------+-------+------+----------------+
|  1 | PRIMARY     | <derived2> | system | NULL               | NULL    | NULL    | NULL  |    1 |                |
|  1 | PRIMARY     | tree       | const  | PRIMARY,index_both | PRIMARY | 4       | const |    1 |                |
|  2 | DERIVED     | NULL       | NULL   | NULL               | NULL    | NULL    | NULL  | NULL | No tables used |
+----+-------------+------------+--------+--------------------+---------+---------+-------+------+----------------+

繰り返しますが、今回はparent_id = @last_id OR parent_id = 5条件のみで、次を取得します。

+----+-------------+------------+--------+-----------------+------+---------+------+------+----------------+
| id | select_type | table      | type   | possible_keys   | key  | key_len | ref  | rows | Extra          |
+----+-------------+------------+--------+-----------------+------+---------+------+------+----------------+
|  1 | PRIMARY     | <derived2> | system | NULL            | NULL | NULL    | NULL |    1 |                |
|  1 | PRIMARY     | tree       | ALL    | index_parent_id | NULL | NULL    | NULL |   10 | Using where    |
|  2 | DERIVED     | NULL       | NULL   | NULL            | NULL | NULL    | NULL | NULL | No tables used |
+----+-------------+------------+--------+-----------------+------+---------+------+------+----------------+

MySQL は、同じクエリで複数のインデックスを処理するのが得意ではありません。AND 条件を使用すると、状況がわずかに改善されます。インデックス ユニオンの最適化よりもindex_mergeの最適化が見られる可能性が高くなります。

バージョンが進むにつれて状況は改善されています5.5が、現在の最新の製品バージョンである version でクエリをテストしたところ、結果は説明どおりです。

これが難しい理由を説明するために、次のことを考慮してください。クエリの 2 つの異なる条件に対して、2 つの異なるインデックスが応答します。1つはid = 5、もう1つは(両方の用語が同じインデックス内から処理されるため、後者のORparent_id = @last_id OR parent_id = 5で問題はありません)。

両方に対応できる単一のインデックスは存在しないため、FORCE INDEX命令は無視されます。参照してください、FORCE INDEXMySQL はテーブル スキャンでインデックスを使用する必要があると言います。テーブルスキャンで複数のインデックスを使用する必要があることを意味するものではありません。

そのため、MySQL はここのドキュメントの規則に従います。しかし、なぜこれがそれほど複雑なのですか?両方のインデックスを使用して応答するため、MySQL は両方から結果を収集する必要があるため、2 番目のインデックスを管理している間、一方を一時バッファに保存します。次に、そのバッファを調べて、同一の行を除外する必要があります (一部の行がすべての条件に適合する可能性があります)。そして、そのバッファをスキャンして結果を返します。

しかし、待ってください、そのバッファ自体はインデックス化されていません。重複をフィルタリングすることは、明らかな作業ではありません。そのため、MySQL は元のテーブルで作業し、そこでスキャンを実行して、混乱を避けることを好みます。

もちろん、これは解決可能です。オラクルのエンジニアはまだこれを改善している可能性があります (最近、彼らはクエリ実行計画の改善に熱心に取り組んでいます) が、これが TODO タスクにあるのか、それとも優先度が高いのかはわかりません。

于 2012-07-10T07:50:22.753 に答える