MySQLソース:JOINシーケンス選択の複雑さ


MySQLオプティマイザコードを見ていると、比較的簡単/コードがはっきりしている部分になるはずです.MySQLオプティマイザには2つの自由度があります.シングルテーブルアクセス方式、マルチテーブル順序選択です.前述したMySQLシングルテーブルアクセスのいくつかの考慮(ref/rangeなど)について説明したが,本稿ではJOINの順序選択における複雑さの解析を紹介する.
複数のテーブルがJOINを必要とする場合、MySQLはまず2つの特殊な状況を処理します.1つは定数テーブルで、1つは外部接続による順序依存関係です.前者は常に関連の一番前に置かれ、後者は遍歴するときに考えられる.本稿では,上記2点を無視して,JOIN順選択時の複雑さをマクロ的に見る.
パラメータが設定されているprune_level(デフォルト)後、MySQLは「極めて」貪欲な方法で順序を取得します.設定されていない場合は、限られた窮挙を使用して「最適」の実行計画を取得します.
1.限られた貧乏行為
有限窮挙はパラメータpruneのみレベルがオフの場合にのみ使用されます.デフォルトではprune_レベル時に開きます.だから、MySQLは普通はそうしません.pruneだけ知りたいならレベルが開いたときは、このセクションを直接スキップして、貪欲なMySQLを参考にします.
パラメータprune_を閉じるlevel後、MySQLは基本的に貧乏で、「有限」とは、関連テーブルの数が63を超えると(search_depthのデフォルト値)、最大深さに達し、MySQLは複数の段階に分けて貧乏になることを意味します.関連テーブルの数が少ない場合(search_depthより小さい場合)、MySQLは可能性をすべて挙げ、JOIN順序ごとのコストを計算し、実行計画として最も低いコストを選択します.この部分のアルゴリズムの複雑さについては、コード注釈に詳しく説明されていますが、関数greedy_を読むことをお勧めします.searchのコメント先.次に、注釈セクションの2つの偽コードを説明します.プロセス全体を説明します.
1.1 greedy_search
 4997     procedure greedy_search
 4998     input: remaining_tables
 4999     output: pplan;
 5000     {
 5001       pplan = ;
 5002       do {
 5003         (t, a) = best_extension(pplan, remaining_tables);
 5004         pplan = concat(pplan, (t, a));
 5005         remaining_tables = remaining_tables - t;
 5006       } while (remaining_tables != {})
 5007       return pplan;
 5008     }

ここでの(t,a)はbest_extensionは、次のJOINが必要なテーブルtを返し、決定されたアクセス方法はaである.上のコードでは、実行計画の拡張は関数best_によって行われます.extension、初期pplanは空で、doループは最終的な実行計画を出力します.
1.2 best_extension
   best_extensionで関数bestを呼び出すextension_by_limited_searchは再帰ループを完了し、その入力は部分実行計画(pplan)とそのコストであり、関数の目的は次の関連テーブルを見つけることである.考え方は簡単で、すべての残りのテーブルを遍歴し、各テーブルに対して、対応する「ローカル」最適実行計画を計算します.もちろん、この「ローカル」最適計算はこの関数を呼び出すので、これは深さ優先の遍歴です.
偽コード(また誰かが私がコードを貼ったと言ったのではないでしょうか):
 5171     @code
 5172     procedure best_extension_by_limited_search(
 5173       pplan in,             // in, partial plan of tables-joined-so-far
 5174       pplan_cost,           // in, cost of pplan
 5175       remaining_tables,     // in, set of tables not referenced in pplan
 5176       best_plan_so_far,     // in/out, best plan found so far
 5177       best_plan_so_far_cost,// in/out, cost of best_plan_so_far
 5178       search_depth)         // in, maximum size of the plans being considered
 5179     {
 5180       for each table T from remaining_tables
 5181       {
 5182         // Calculate the cost of using table T as above
 5183         cost = complex-series-of-calculations;
 5184
 5185         // Add the cost to the cost so far.
 5186         pplan_cost+= cost;
 5187
 5188         if (pplan_cost >= best_plan_so_far_cost)
 5189           // pplan_cost already too great, stop search
 5190           continue;
 5191
 5192         pplan= expand pplan by best_access_method;
 5193         remaining_tables= remaining_tables - table T;
 5194         if (remaining_tables is not an empty set
 5195             and
 5196             search_depth > 1)
 5197         {
 5198           best_extension_by_limited_search(pplan, pplan_cost,
 5199                                            remaining_tables,
 5200                                            best_plan_so_far,
 5201                                            best_plan_so_far_cost,
 5202                                            search_depth - 1);
 5203         }
 5204         else
 5205         {
 5206           best_plan_so_far_cost= pplan_cost;
 5207           best_plan_so_far= pplan;
 5208         }
 5209       }
 5210     }
 5211     @endcode


1つの説明:遍歴するたびに、現在の最適コストよりもコストが大きいことが発見されると、あきらめて深く進まない.
1.3簡単なまとめ
     :
	       partial plan
	N    
    :
	  N   search_depth,  search_depth         ,          
		       ,   :              N-depth

1.4複雑度分析
   
したがって、複雑度はO(N*N^search_depth/search_depth)である可能性がある.もしsearch_depth>Nではアルゴリズムの複雑さはO(N!)である.通常MySQLオプティマイザ解析の複雑さはO(N!)です.
1.5境界状況
極端なケースは2つあります.
-JOINが必要なテーブルの数がsearchより小さい場合depthの場合,ここでは深さ優先の貧挙に退化して最適実行計画を決定する.
-search_depth=1の場合、関数は「極めて」貪欲なアルゴリズムに劣化し、現在の残りのテーブルからコストを最小限に抑え、現在の実行計画を拡張します.
残りの状況は上の2つの間にある.
2.欲張りMySQL
パラメータprune_を開いたlevel(デフォルトでオン)にすると、MySQLは実行計画を窮屈な方法で拡張するのではなく、残りのテーブルで最も少ないレコード数にアクセスするテーブルを直接選択します.MySQLマニュアルによると、経験的には、このような「educated guess」は、最適な実行計画をほとんど漏らさないが、検索空間を大幅に縮小することができるという.最適な実行計画が漏れているのではないかと疑う場合は、パラメータを閉じてみてください.もちろん、検索空間が大きくなり、オプティマイザの実行時間が長くなります.
このパラメータは深さ優先探索に役立ち,深さ探索を行う際にcurrent_に基づいてrecord_countとcurrent_read_time、これが良い実行計画かどうかを確定します.(本来は計算コスト決定を再帰的に呼び出す必要がある)
以下に、簡単な疑似コードの説明を示します.
  :
pplan 			        (    ) short for partial plan
N remaining table 	     (    ,            )
	 N   T[0] T[1] ... T[N-1]

    :
Function best_extension(pplan,N)
Foreach T in T[0...N-1]
    let P(pplan,T) := add T to pplan
    let current_record_count := #row of P(pplan,T)
    let current_read_time := #read time of P(pplan,T)
    if [
         T is Not The First Table in T[0...N-1] AND
         current_record_count >= best_record_count AND
         current_read_time >= best_read_time
       ]
        "P(pplan,T) is a bad plan! SKIP it!!!!!!!"
    END
    let best_record_count := min(best_record_count, current_record_count )
    let best_read_time := min(best_read_time,current_read_time)
    best_extension(P(pplan,T),N-1);
END

説明:
(1)擬似コードに依存関係は考慮されていない.最初のテーブルのCOSTはいつも計算されます.
(2)pplanとT[0…N-1]に対してpplanとT[0]、T[1]…T[N-1]との関連付け後のそれぞれのcurrent_のみを計算するrecord_count、それに基づいて、pplanはどのテーブルJOINに従うべきかを選択します.最初のテーブル(検索ツリーの一番左のブランチ)がその代価を計算するのを除いて、他のすべてのブランチはトンボが水を点すように1級だけ計算され、深さの再帰計算は行われません.
(3)これは非常に急進的な最適化方式であるように見える.
3.開始前のソート
 4753   my_qsort(  join->best_ref +   join->const_tables,
 4754              join->tables -   join->const_tables, sizeof(  JOIN_TAB*),
 4755            straight_  join ?   join_tab_cmp_straight :   join_tab_cmp);

MySQLは、JOIN順序の決定を開始する前に、テーブルごとにアクセス可能なレコード数に基づいて、一度ソートします.このステップは余計に見えますが、貧しい検索を行うと、計画を実行するために探知する必要がある深さを大幅に減らすことができます.
実行計画を評価するとき、現在のcostが最適な実行計画よりも大きいことがステップで発見された場合、すぐに評価を終了します.これは、最適な実行計画が最初に見つかった場合、評価が少なくなることを意味します.テーブルがスキャンする必要があるロー数が少ないほど、先に使用すればするほど良いと考えられます.もちろん、ここでのソート評価ではJOIN条件は使用されていないので、スキャンが多く必要なように見えますが、JOINを加えて以降はスキャンが少ない記録しか必要ないかもしれません.
4.関数呼び出しスタック
#0            best_access_path
#1          best_extension_by_limited_search
#2        greedy_search
#3      choose_plan
#4    make_join_statistics
#5  JOIN::optimize
関連記事
  • 2008-12-05--MySQLにおけるJoinの基本実現原理
  • 2009-11-08--MySQLにおけるLEFTJOINのメインテーブル
  • 2009-04-14--MySqlランダム読み出しデータ
  • 2012-03-26--Mysqlソース学習-Thread Manager
  • 2008-10-30--limitクエリーを使用しながら合計レコード数を取得:SQL_CALC_FOUND_ROWSとFOUND_ROWS()
  • 2012-02-13--タイプ変換がMySQL選択インデックスに与える影響
  • 2010-06-01--mysql server上の小さなテクニック
  • 2012-12-23--MySQL TPCHテストツールパンフレット
  • 2012-12-05--初心者必見:一歩で到着するInnoDB
  • 2012-12-05--MySQL最適化のDiscuzフォーラムMySQL汎用最適化
  • ラベル:join,MySQLソース分析