最後のMysql高速クエリの秘密–B+ツリーインデックスの理解
インデックス作成のコスト
- スペースのコスト
インデックスを作成するたびに、そのインデックス用にB+ツリーを作成する必要があります。各B+ツリーの各ノードはデータページであり、ページはデフォルトで16KBのストレージスペース(大きなB +ツリー)を占有します。多くのデータページ。 - 時間コスト
テーブル内のデータを追加、削除、または変更するたびに、各B+ツリーインデックスを変更する必要があります。
追加、削除、および変更操作はノードとレコードの順序を損なう可能性があるため、ストレージエンジンは、ノードとレコードの順序を維持するために、レコードシフト、ページ分割、ページリサイクルなどの一部の操作を実行するために追加の時間を必要とします。
テーブルに作成されるインデックスが多いほど、占有されるストレージスペースが多くなり、レコードの追加、削除、および変更時のパフォーマンスが低下します。
2.B+ツリーインデックスの条件
テーブルを作成します。
CREATE TABLE person_info(
id INT NOT NULL auto_increment,
name VARCHAR(100) NOT NULL,
birthday DATE NOT NULL,
phone_number CHAR(11) NOT NULL,
country varchar(100) NOT NULL,
PRIMARY KEY (id),
KEY idx_name_birthday_phone_number (name, birthday, phone_number)
);
- InnoDBストレージエンジンは、id列のクラスター化インデックスを自動的に構築します。
- セカンダリインデックスidx_name_birthday_phone_numberは、3つの列で構成される共同インデックスです。したがって
、このインデックスに対応するB +ツリーのリーフノードに保存されているユーザーレコードは、name、birthday、phone_number、およびプライマリキーIDの値の3つの列の値のみを保持し、国の列。 - テーブル内のインデックスと同じ数のB+ツリーがあり、person_infoテーブルはクラスター化インデックスとidx_name_birthday_phone_numberインデックス用に2つのB+ツリーを構築します。
最初に名前列の値で並べ替えます。
name列の値が同じである場合は、birthday列の値で並べ替えます。
誕生日の列の値も同じである場合は、phone_numberの値で並べ替えます。
1.完全な値の一致
検索条件の列がインデックス列と同じである場合、この状況はすべて値の一致と呼ばれます。
SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday = '1990-09-27' AND phone_number = '15123983239';
Q:WHERE句のいくつかの検索条件の順序は、クエリ結果に影響しますか?
A:影響はありません。MySQLには、これらの検索条件を分析し、使用できるインデックスの列の順序に従って、最初に使用する検索条件と後で使用する検索条件を決定するクエリオプティマイザがあります。
2.左側の列を一致させます
また、検索ステートメントのジョイントインデックスにすべての列を含めることはできず、左側の列のみを含めることができます。
SELECT * FROM person_info WHERE name = 'Ashburn';
#或者包含多个左边的列也行
SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday = '1990-09-27';
Q:このB +ツリーインデックスを使用するには、なぜ左側の列が検索条件に表示される必要があるのですか?
#比如下边的语句就用不到这个B+树索引
SELECT * FROM person_info WHERE birthday = '1990-09-27';
回答:B +ツリーのデータページとレコードは、最初に名前列の値に従って並べ替えられ、誕生日列は、名前列の値が同じである場合の並べ替えに使用されます。つまり、名前列の値が異なるレコードの誕生日は順序付けされていない可能性があります。
ジョイントインデックスでできるだけ多くの列を使用する場合、検索条件の各列はジョイントインデックスからのものである必要があります左端の連続する列。
3.列のプレフィックスを一致させます
文字列の場合、その順序は、比較ルールが文字のサイズを1つずつ比較することです。
つまり、これらの文字列の最初のn文字、つまりプレフィックスはすべて並べ替えられているため、文字列タイプのインデックス列の場合、プレフィックスを照合するだけでレコードをすばやく見つけることもできます。
たとえば、名前が「As」で始まるレコードをクエリする場合は、次のようなクエリを記述できます。
SELECT * FROM person_info WHERE name LIKE 'As%';
接尾辞または中央の文字列のみが指定されている場合、文字列の中央に「As」が含まれる文字列はソートされないため、MySQLはレコード位置をすばやく見つけることができず、テーブル全体をスキャンすることしかできません。
SELECT * FROM person_info WHERE name LIKE '%As%';
特定の文字列サフィックスに一致する必要がある場合があります。たとえば、comでサフィックスをクエリする場合、テーブルにurl列があり、url列にインデックスが作成されていると仮定すると、多くのurlが格納されます。クエリ条件次のように記述できますがWHERE url LIKE '%com'
、この場合、url列のインデックスは使用できません。サフィックスクエリをプレフィックスクエリに書き換えることはできますが、テーブル内のすべてのデータを逆の順序で保存する必要があります。
つまり、次のようにurl列にデータを保存できます。インデックスを使用できるように検索時:
WHERE url LIKE 'moc%'
4.範囲値を一致させる
インデックス付きのB+ツリーでは、すべてのレコードがインデックス列の値に従って小さいものから大きいものへと並べ替えられるため、インデックス列の値が特定の範囲内にあるレコードを見つけるのに非常に便利です。
SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow';
B +ツリーのデータページとレコードは最初に名前列で並べ替えられるため、上記のクエリプロセスは実際には次のようになります。
- Asaという名前のレコードを検索します。
- Barlowという名前のレコードを見つけます。
- すべてのレコードがリンクリストで接続されているため(シングルリンクリストはレコード間で使用され、ダブルリンクリストはデータページ間で使用されます)、レコード間のレコードは簡単に取得できます。
- これらのレコードの主キー値を見つけてから、クラスター化インデックスのテーブルに戻って、完全なレコードを見つけます。
範囲検索にユニオンを使用する場合、範囲検索が複数の列で同時に実行される場合、B +ツリーインデックスは、範囲検索がインデックスの左端の列で実行される場合にのみ使用できることに注意してください。
#这个查询中通过name进行范围查找的记录中可能并不是按照birthday列进行排序的,所以在搜索条件中继续以birthday列进行查找时是用不到这个B+树索引的。
SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow' AND birthday > '1980-01-01';
5.列と完全に一致し、範囲は別の列と一致します
SELECT * FROM person_info WHERE name = 'Ashburn' AND birthday > '1980-01-01' AND birthday< '2000-12-31' AND phone_number > '15100000000';
- 名前列を正確に検索するには、B+ツリーインデックスを使用できます。
- 名前の列は正確な検索であるため、name ='Ashburn'条件付き検索によって取得された結果の名前の値はすべて同じであり、誕生日の値に従って並べ替えられます。したがって、この時点でB+ツリーインデックスを使用して誕生日列の範囲検索を実行できます。
- 誕生日の範囲によって検出されたレコードの誕生日の値が異なる可能性があるため、この条件ではB+ツリーインデックスを使用できなくなります。
6.ソート用
多くの場合、ORDER BY句を使用して、特定のルールに従ってクエリされたレコードを並べ替える必要があります。通常、レコードをメモリにロードしてから、いくつかの並べ替えアルゴリズムを使用してメモリ内のこれらのレコードを並べ替えることしかできません。
#查询的结果集需要先按照name值排序,如果记录的name值相同,则需要按照birthday来排序,如果birthday的值相同,则需要按照phone_number排序。
SELECT * FROM person_info ORDER BY name, birthday, phone_number LIMIT 10;
ORDER BY句に続く列の順序も、インデックス付きの列の順序で指定する必要があります。
インデックスの左側の列に一致するORDERBY名、ORDER BY名、誕生日の形式では、部分的なB+ツリーインデックスを使用できます。
インデックス付けを並べ替えに使用できないいくつかのケース
- ASCとDESCの混合使用
並べ替えに共同インデックスを使用するシナリオでは、各並べ替え列の並べ替え順序が一貫している必要があります。つまり、各列がASCルールで並べ替えられるか、すべての列がDESCルールで並べ替えられます。 。 - 非ソートに使用されるインデックス列がWHERE句に含まれている場合でも、インデックスはソートに使用されません。
- ソートに使用される複数の列はインデックスに含まれておらず、この場合、インデックスをソートに使用することはできません。
- インデックスを使用した並べ替え操作の場合、インデックス列が複雑な式ではなく、別の列の形式で表示されることを確認する必要があります
#不能使用索引进行排序
SELECT * FROM person_info ORDER BY UPPER(name) LIMIT 10;
7.グループ化用
例えば:
SELECT name, birthday, phone_number, COUNT(*) FROM person_info GROUP BY name, birthday, phone_number
- まず、レコードは名前の値に従ってグループ化され、同じ名前の値を持つすべてのレコードがグループに分割されます。
- 同じ名前の値を持つ各グループのレコードは誕生日の値に従ってグループ化され、同じ誕生日の値を持つレコードは小さなグループに入れられるため、大きなグループは多くの小さなグループに分割されているように見えます。
- 次に、前の手順で生成された小グループをphone_numberの値に従って小グループに分割します。これにより、全体的な外観は、レコードを1つの大グループに分割し、次に大グループを複数の小グループに分割し、次にいくつかの小グループを分割するようになります。 。グループは、より小さなサブグループに細分されます。
インデックスがない場合は、このグループ化プロセスをメモリに実装する必要があります。インデックスがある場合、グループ化の順序はB +ツリーのインデックス列の順序と一致し、 B+ツリーのインデックスは次のように並べ替えられます。インデックス列に、グループ化にB+ツリーインデックスを直接使用できます。
グループ化列の順序も、インデックス列の順序と一致している必要があります。そうでない場合は、インデックス列の左側の列のみをグループ化に使用できます。
3.フォームを返送するための費用
テーブルに返す必要のあるレコードが多いほど、セカンダリインデックスを使用するパフォーマンスが低下し、一部のクエリでは、セカンダリインデックスの代わりに全表スキャンを使用するようになります。
Q:全表スキャンを使用する場合とセカンダリインデックス+リターンテーブルを使用してクエリを実行する場合はいつですか?
回答:クエリオプティマイザは、テーブル内のレコードの統計データを事前に計算し、これらの統計データを使用して、クエリ条件に従ってテーブルに返す必要のあるレコードの数を計算します。テーブルに返される場合は、フルテーブルスキャンを使用する可能性が高く、代わりにセカンダリインデックス+リターンテーブルメソッドを使用する傾向があります。通常の状況では、クエリによって取得されるレコードの数を制限すると、オプティマイザはクエリにセカンダリインデックス+リターンテーブルを使用する傾向が強くなります。
例えば:
SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow' LIMIT 10;
#添加了 LIMIT 10 的查询更容易让优化器采用二级索引 + 回表的方式进行查询。
SELECT * FROM person_info ORDER BY name, birthday, phone_number;
#由于查询列表是 * ,所以如果使用二级索引进行排序的话,需要把排序完的二级索引记录全部进行回表操作
#这样操作的成本还不如直接遍历聚簇索引然后再进行文件排序低
#所以优化器会倾向于使用全表扫描的方式执行查询。
#如果我们加了LIMIT子句,比如:
SELECT * FROM person_info ORDER BY name, birthday, phone_number LIMIT 10;
#这样需要回表的记录特别少,优化器就会倾向于使用二级索引 + 回表的方式执行查询。
テーブルの戻り操作によって引き起こされるパフォーマンスの低下に完全に別れを告げるために、クエリリストにインデックス列のみを含めることをお勧めします。
idx_name_birthday_phone_numberインデックスを介して結果を取得した後、name、birthday、phone_numberの3つのインデックス列の値のみをクエリする場合、レコードの残りの列を見つけるためにクラスター化インデックスに移動する必要はありません。 、保存する国列の値戻りテーブル操作によって引き起こされるパフォーマンスの低下が排除されます。インデックスのみを使用するこのクエリメソッドを呼び出します。インデックスカバレッジ。
クエリリストとして*を使用することはお勧めしません。また、順番にクエリする必要のある列を指定することをお勧めします。
第四に、注意が必要な事項
- 検索、並べ替え、またはグループ化に使用される列にのみインデックスを作成します。
- 列のカーディナリティが大きい列のインデックスを作成します。
- インデックス列のタイプはできるだけ小さくする必要があります。
- 文字列値のプレフィックスのみにインデックスを付けることができます。
- インデックスは、インデックス付きの列が比較式に単独で表示される場合にのみ適用できます。
- クラスタ化インデックスのページ分割とレコードシフトを最小限に抑えるために、主キーにAUTO_INCREMENT属性を設定することをお勧めします。
- テーブル内の重複および冗長なインデックスを見つけて削除します。
- テーブルを返すことによって引き起こされるパフォーマンスの低下を回避するために、クエリにカバーインデックスを使用するようにしてください。