(偽の)人の名前を含む、それぞれ50,000行のテーブルが2つあるsqliteデータベースがあります。両方のテーブルに共通する名前(ギブネーム、ミドルネームのイニシャル、姓)がいくつあるかを確認する簡単なクエリを作成しました。
select count(*) from fakenames_uk inner join fakenames_usa on fakenames_uk.givenname=fakenames_usa.givenname and fakenames_uk.surname=fakenames_usa.surname and fakenames_uk.middleinitial=fakenames_usa.middleinitial;
(このクエリに関係なく)主キー以外にインデックスがない場合は、すばやく実行されます。
[james@marlon Downloads] $ time sqlite3 generic_data_no_indexes.sqlite "select count(*) from fakenames_uk inner join fakenames_usa on fakenames_uk.givenname=fakenames_usa.givenname and fakenames_uk.surname=fakenames_usa.surname and fakenames_uk.middleinitial=fakenames_usa.middleinitial;"
131
real 0m0.115s
user 0m0.111s
sys 0m0.004s
しかし、各テーブルの3つの列にインデックスを追加すると(全部で6つのインデックス):
CREATE INDEX `idx_uk_givenname` ON `fakenames_uk` (`givenname` )
//etc.
その後、ゆっくりと実行されます。
[james@marlon Downloads] $ time sqlite3 generic_data.sqlite "select count(*) from fakenames_uk inner join fakenames_usa on fakenames_uk.givenname=fakenames_usa.givenname and fakenames_uk.surname=fakenames_usa.surname and fakenames_uk.middleinitial=fakenames_usa.middleinitial;"
131
real 1m43.102s
user 0m52.397s
sys 0m50.696s
これには韻や理由がありますか?
インデックスのないバージョンのEXPLAIN QUERY PLAN
の結果は次のとおりです。
0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING AUTOMATIC COVERING INDEX (middleinitial=? AND surname=? AND givenname=?)
これはインデックス付きです:
0|0|0|SCAN TABLE fakenames_uk
0|1|1|SEARCH TABLE fakenames_usa USING INDEX idx_us_middleinitial (middleinitial=?)
SQLiteでは、結合はネストされたループ結合として実行されます。つまり、データベースは1つのテーブルを通過し、各行について、他のテーブルから一致する行を検索します。
インデックスがある場合、データベースはインデックス内の一致をすばやく検索し、対応するテーブル行に移動して、必要な他の列の値を取得できます。
この場合、3つの可能なインデックスがあります。統計情報( [〜#〜] analyze [〜#〜] を実行すると作成される)がない場合、データベースはI/Oを削減するために最小のものを選択します。ただし、middleinitial
インデックスは、フェッチする必要があるテーブル行の数を大幅に削減しないため、役に立ちません。さらに、インデックスを介した追加のステップにより、テーブル行が順番にではなくランダムに読み取られるため、必要なI/Oが実際に増加します。
インデックスがない場合、一致する行のルックアップでは、最初のテーブルの各行について2番目のテーブルの完全なテーブルスキャンが必要になります。これは非常に悪いので、このクエリのためだけに一時インデックスを作成してから削除する価値があるとデータベースは推定しています。この一時( "AUTOMATIC")インデックスは、検索に使用されるすべての列に作成されます。 COUNT(*)操作は他の列の値を必要としないため、このインデックスは偶然にも covering index になります。これは、インデックスに対応するテーブル行を実際に検索する必要がないことを意味しますエントリ。さらに多くのI/Oを節約します。
このクエリを高速化するには、このインデックスを永続的に作成します。これにより、一時的なインデックスを作成する必要がなくなります。
CREATE INDEX uk_all_names ON fakenames_uk(surname, givenname, middleinitial);
EXPLAIN QUERY PLAN
SELECT count(*)
FROM fakenames_uk
JOIN fakenames_usa USING (givenname, middleinitial, surname);
0|0|1|SCAN TABLE fakenames_usa
0|1|0|SEARCH TABLE fakenames_uk USING COVERING INDEX uk_all_names (surname=? AND givenname=? AND middleinitial=?)
surname
のインデックスは不要になりました。3列のインデックスをこの列のルックアップに使用できるためです。givenname
のインデックスは、この列のみを検索する場合に役立つことがあります。middleinitial
のインデックスは常に意味がありません。テーブル全体をスキャンするだけの場合、26の可能な値の1つを検索するクエリの方が高速です。