MySQLクエリの最適化について言及する場合、インデックスは最初に取り上げられるものの1つです。今日は、なぜそれらがそれほど重要なのかを見ていきます。
インデックスとは何ですか?
一般に、インデックスは、レコードが言及されているページへの参照を含むレコードのアルファベット順のリストです。 MySQLでは、インデックスは行をすばやく見つけるために使用されるデータ構造です。インデックスはキーとも呼ばれ、これらのキーはパフォーマンスを向上させるために重要です。データが大きくなるにつれて、インデックスを適切に使用する必要性がますます重要になる可能性があります。インデックスの使用は、クエリのパフォーマンスを向上させる最も強力な方法の1つです。インデックスを適切に使用すると、クエリのパフォーマンスが数十倍、場合によっては数百倍も向上する可能性があります。
今日は、MySQLでインデックスを使用することの基本的な利点と欠点について説明します。 MySQLインデックスだけでも本全体に値するので、この投稿は完全にすべてを網羅しているわけではありませんが、それは良い出発点になることを覚えておいてください。インデックスがより深いレベルでどのように機能するかに興味がある人は、TapioLahdenmäkiとMichaelLeachによる『Relational Database Index Design and Optimizers』という本を読むと、より多くの洞察が得られるはずです。
MySQLでインデックスを使用する主な利点はいくつかあり、次のとおりです。
- インデックスを使用すると、WHERE句に一致する行をすばやく見つけることができます。
- インデックスは、クエリが特定の行を検索しないようにするのに役立つため、サーバーが調べる必要のあるデータの量を減らすことができます。複数のインデックスから選択できる場合、MySQLは通常、最も選択的なインデックスを使用します。最小量の行を見つけるインデックス;
- JOIN操作で他のテーブルから行を取得するために、インデックスが使用される場合があります。
- インデックスは、インデックスを使用する特定の列の最小値または最大値を見つけるために使用される場合があります。
- インデックスは、操作がインデックスの左端のプレフィックスで実行される場合、テーブルの並べ替えまたはグループ化に使用される場合があります。同様に、複数列のインデックスの左端のプレフィックスもクエリオプティマイザによって使用される場合があります。行を検索するには;
- インデックスはディスクI/Oを保存するためにも使用される場合があります-カバーインデックスが使用されている場合、クエリはディスクI/Oを保存するインデックス構造から直接値を返すことができます。
同様に、インデックスには複数の種類があります:
- INDEXは、値が一意である必要がないタイプのインデックスです。このタイプのインデックスはNULL値を受け入れます;
- UNIQUE INDEXは、テーブルから重複する行を削除するために頻繁に使用されます。このタイプのインデックスを使用すると、開発者は行の値の一意性を強制できます。
- FULLTEXT INDEXは、全文検索機能を利用するフィールドに適用されるインデックスです。このタイプのインデックスは、値をインデックス内の値と直接比較するのではなく、テキスト内のキーワードを検索します。
- DESCENDING INDEXは、行を降順で格納するインデックスです。クエリオプティマイザは、クエリによって降順が要求されたときに、このタイプのインデックスを選択します。このインデックスタイプはMySQL8.0で導入されました;
- PRIMARYKEYもインデックスです。簡単に言うと、PRIMARY KEYは、テーブルの各行を識別する列または列のセットです。AUTO_INCREMENT属性を持つフィールドと一緒に使用されることがよくあります。このタイプのインデックスはNULL値を受け入れず、一度設定すると、PRIMARYKEYの値を変更できません。
ここで、MySQLでインデックスを使用することの利点と欠点の両方について説明します。おそらく最も頻繁に議論される逆さまから始めます-WHERE句に一致するクエリを高速化します。
インデックスは、WHERE句に一致する検索クエリを高速化するために頻繁に使用されます。インデックスによってこのような検索操作が高速化される理由は非常に単純です。インデックスを使用するクエリは、全表スキャンを回避します。
WHERE句に一致するクエリを高速化するために、MySQLのEXPLAINステートメントを利用できます。 EXPLAIN SELECTステートメントは、MySQLクエリオプティマイザがクエリを実行する方法についての洞察を提供する必要があります。また、問題のクエリがインデックスを使用しているかどうか、およびどのインデックスを使用しているかを示すこともできます。次のクエリの説明を見てください:
mysql> EXPLAIN SELECT * FROM demo_table WHERE field_1 = “Demo” \G;
*************************** 1. row ***************************
<...>
possible_keys: NULL
key: NULL
key_len: NULL
<...>
mysql> EXPLAIN SELECT * FROM demo_table WHERE field_1 = “Demo” \G;
*************************** 1. row ***************************
<...>
possible_keys: field_1
key: field_1
key_len: 43
<...>
possible_keys列はMySQLが選択できるインデックスを示し、key列は実際に選択されたインデックスを示し、key_len列は選択されたキーの長さを示します。
この場合、MySQLはインデックス内の値のルックアップを実行し、指定された値を含むすべての行を返します。その結果、クエリが高速になります。インデックスは特定のクエリを高速化するのに役立ちますが、インデックスをクエリに役立てたい場合は、次の点に注意する必要があります。
- 列を分離する-インデックスが使用されている列が分離されていない場合、MySQLはインデックスを使用できません。たとえば、次のようなクエリではインデックスは使用されません。
SELECT field_1 FROM demo_table WHERE field_1 + 5 = 10;
これを解決するには、WHERE句の後に続く列をそのままにします。クエリを可能な限り単純化し、列を分離します。
- 前にワイルドカードがあるLIKEクエリの使用は避けてください。この場合、前にあるワイルドカードはテキストの前に何かがある可能性があることを意味するため、MySQLはインデックスを使用しません。ワイルドカードでLIKEクエリを使用する必要があり、クエリでインデックスを使用する必要がある場合は、ワイルドカードが検索ステートメントの最後にあることを確認してください。
もちろん、WHERE句に一致するクエリの高速化は、他の方法(たとえば、パーティショニング)でも実行できますが、簡単にするために、この投稿ではこれについて詳しく説明しません。
ただし、関心があるのはさまざまな種類のインデックスタイプなので、ここで調べます。
MySQLのUNIQUEインデックスの目的は、列の値の一意性を強制することです。 UNIQUEインデックスを使用するには、CREATEUNIQUEINDEXクエリを実行します。
CREATE UNIQUE INDEX demo_index ON demo_table(demo_column);
You can also create a unique index when you create a table:
CREATE TABLE demo_table (
`demo_column` VARCHAR(100) NOT NULL,
UNIQUE KEY(demo_column)
);
#1062 - Duplicate entry ‘Demo’ for key ‘demo_column’
FULLTEXTインデックスは、全文検索機能を使用する列に適用されるインデックスです。このタイプのインデックスには、ストップワードや検索モードなど、多くの独自の機能があります。
InnoDBストップワードリストには36ワード、MyISAMストップワードリストには143ワードがあります。InnoDBでは、ストップワードは変数innodb_ft_user_stopword_tableに設定されたテーブルから派生します。それ以外の場合、この変数が設定されていない場合は派生します。 innodb_ft_server_stopword_table変数から。これらの2つの変数のどちらも設定されていない場合、InnoDBは組み込みリストを使用します。デフォルトのInnoDBストップワードリストを表示するには、INNODB_FT_DEFAULT_STOPWORDテーブルをクエリします。
MyISAMでは、ストップワードはstorage / myisam/ft_static.cファイルから取得されます。 ft_stopword_file変数を使用すると、デフォルトのストップワードリストを変更できます。この変数が空の文字列に設定されている場合、ストップワードは無効になりますが、この変数がファイルを定義している場合、定義されたファイルはコメントのために解析されないことに注意してください-MyISAMはファイル内で見つかったすべてのワードをストップワードとして扱います。
FULLTEXTインデックスは、その独自の検索モードでも有名です:
- 修飾子のない全文検索クエリを実行すると、自然言語モードがアクティブになります。自然言語モードは、IN NATURALLANGUAGEMODE修飾子を使用してアクティブにすることもできます。
- WITH QUERY EXPANSION修飾子は、クエリ拡張を使用した検索モードを有効にします。このような検索モードは、検索を2回実行することで機能し、2回目の検索を実行すると、結果セットには最初の検索で最も関連性の高いドキュメントがいくつか含まれます。一般に、この修飾子は、ユーザーが暗黙の知識を持っている場合に役立ちます(たとえば、ユーザーが「データベース」を検索し、結果セットに「InnoDB」と「MyISAM」を表示したい場合)。
- IN BOOLEAN MODE修飾子を使用すると、ブール演算子を使用して検索できます。たとえば、+、-、または*演算子はそれぞれ異なるタスクを実行します-+演算子は値が行に存在する必要があることを定義し、-演算子は値が存在してはならないことを定義し、*演算子はワイルドカード。
FULLTEXTインデックスを使用するクエリは次のようになります:
SELECT * FROM demo_table WHERE MATCH(demo_field) AGAINST(‘value’ IN NATURAL LANGUAGE MODE);
FULLTEXTインデックスは、一般的にMATCH()AGAINST()操作に役立ちます。WHERE操作ではなく、WHERE句が使用される場合は異なるインデックスタイプを使用することの有用性は失われません。
FULLTEXTインデックスの文字の最小長も言及する価値があります。 InnoDBでは、全文検索は、検索クエリが3文字以上で構成されている場合にのみ実行できます。この制限は、MyISAMストレージエンジンでは4文字に引き上げられています。
DESCENDINGインデックスは、InnoDBがエントリを降順で格納するようなインデックスです。クエリオプティマイザーは、クエリによって降順が要求されたときにこのようなインデックスを使用します。このようなインデックスは、次のようなクエリを実行することで列に追加できます。
CREATE INDEX descending_index ON demo_table(column_name DESC);
PRIMARY KEYは、テーブルの各行の一意の識別子として機能します。 PRIMARY KEYを含む列には、一意の値が含まれている必要があります。NULL値も使用できません。 PRIMARY KEYを持つ列に重複する値が追加されると、MySQLはエラー#1062で応答します:
#1062 - Duplicate entry ‘Demo’ for key ‘PRIMARY’
NULL値が列に追加されると、MySQLはエラー#1048で応答します:
#1048 - Column ‘id’ cannot be null
プライマリインデックスは、クラスター化インデックスと呼ばれることもあります(後で説明します)。
複数の列のインデックスは誤解されることがよくあります。開発者とDBAは、すべての列に個別にインデックスを付けたり、間違った順序でインデックスを付けたりすることがあります。複数列のインデックスを使用するクエリを可能な限り効果的にするために、複数の列を使用するインデックスの列の順序は、このスペースでの混乱の最も一般的な原因の1つであることに注意してください。 」インデックスの順序ソリューションでは、複数列のインデックスの正しい順序は、インデックスを使用しているクエリに依存することを覚えておく必要があります。これは非常に明白に思えるかもしれませんが、複数列のインデックスを処理する場合は列の順序が重要であることを忘れないでください。最も頻繁に実行されるクエリに対して可能な限り選択的になるように列の順序を選択してください。
特定の列の選択性を測定するには、テーブル内の行の総数に対する個別のインデックス値の数の比率を取得します。選択性の高い列が最初の列である必要があります。 。
非常に長い文字列にインデックスを付ける必要がある場合もあります。その場合、値全体ではなく、最初の数文字(プレフィックス)にインデックスを付けることで、時間とリソースを節約できることがよくあります。
>プレフィックスインデックスは、列に非常に長い文字列値が含まれている場合に役立ちます。つまり、列全体にインデックスを追加すると、多くのディスク領域が消費されます。 MySQLは、値のプレフィックスのみにインデックスを付けることができるようにすることで、この問題に対処するのに役立ちます。これにより、インデックスサイズが小さくなります。ご覧ください:
CREATE TABLE `demo_table` (
`demo_column` VARCHAR(100) NOT NULL,
INDEX(demo_column(10))
);
上記のクエリは、デモ列にプレフィックスインデックスを作成し、値の最初の10文字のみをインデックス付けします。既存のテーブルにプレフィックスインデックスを追加することもできます:
CREATE INDEX index_name ON table_name(column_name(length));
たとえば、demo_tableのdemo_columnの最初の5文字にインデックスを付ける場合は、次のクエリを実行できます。
CREATE INDEX demo_index ON demo_table(demo_column(5));
選択性を与えるのに十分な長さであるが、スペースを与えるのに十分短いプレフィックスを選択する必要があります。ただし、これは口で言うほど簡単ではないかもしれません。自分に合った解決策を実験して見つける必要があります。
カバーインデックスは、クエリを実行するために必要なすべてのフィールドを「カバー」します。つまり、クエリ内のすべてのフィールドがインデックスでカバーされている場合、カバーインデックスが使用されています。たとえば、次のようなクエリの場合:
SELECT id, title FROM demo_table WHERE id = 1;
INDEX index_name(id, title);
クエリがカバーインデックスを使用していることを確認する場合は、EXPLAINステートメントを発行してから、Extra列を確認してください。たとえば、テーブルにidとtitleに複数列のインデックスがあり、これら2つの列のみにアクセスするクエリが実行された場合、MySQLはインデックスを使用します。
mysql> EXPLAIN SELECT id, title FROM demo_table \G;
*************************** 1. row ***************************
<...>
type: index
key: index_name
key_len: 5
rows: 1000
Extra: Using index
<...>
カバーするインデックスは、カバーする列の値を格納する必要があることに注意してください。つまり、他の種類のインデックスはこれらの値を格納しないため、MySQLはクエリをカバーするためにBツリーインデックスのみを使用できます。
クラスター化インデックス、セカンダリインデックス、およびインデックスカーディナリティ
インデックスについて説明するとき、クラスター化、セカンダリインデックス、およびインデックスカーディナリティという用語も聞こえる場合があります。簡単に言うと、クラスター化インデックスはデータストレージへのアプローチであり、クラスター化インデックス以外のすべてのインデックスはセカンダリインデックスです。一方、インデックスのカーディナリティは、インデックス内の一意の値の数です。
クラスター化されたインデックスは、近い値もディスク上に互いに近くに格納されるため、クエリを高速化しますが、それが、テーブルにクラスター化されたインデックスを1つしか持てない理由でもあります。
セカンダリインデックスは、プライマリインデックスではないインデックスです。このようなインデックスには重複がある可能性があります。
インデックスの使用には確かに利点がありますが、インデックスがMySQLの問題の主な原因の1つである可能性があることを忘れてはなりません。インデックスを使用することのいくつかの欠点は次のとおりです。
- インデックスは特定のクエリのパフォーマンスを低下させる可能性があります-インデックスはSELECTクエリのパフォーマンスを高速化する傾向がありますが、データが更新されるとINSERT、UPDATE、およびDELETEクエリのパフォーマンスを低下させるためです。インデックスも一緒に更新する必要があります。インデックスの操作を伴う操作は通常よりも遅くなります。
- インデックスはディスクスペースを消費します-インデックスはそれ自体のスペースを占有するため、インデックス付きデータもより多くのディスクスペースを消費します;
- 冗長で重複したインデックスが問題になる可能性があります-MySQLでは、列に重複したインデックスを作成できますが、そのような間違いから「保護」することはできません。次の例を見てください。
CREATE TABLE `demo_table` ( `id` INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, `column_2` VARCHAR(10) NOT NULL, `column_3` VARCHAR(10) NOT NULL, INDEX(id), UNIQUE(id) );
経験の浅いユーザーは、このクエリによってid列が自動的にインクリメントされ、列にインデックスが追加され、列が重複する値を受け入れないようになると考えるかもしれません。ただし、これはここで起こっていることではありません。この場合、同じ列には3つのインデックスがあります。通常のINDEXです。MySQLはインデックスを使用してPRIMARY KEY制約とUNIQUE制約の両方を実装しているため、同じ列にさらに2つのインデックスが追加されます。
結論として、MySQLのインデックスには独自の場所があります。インデックスはさまざまなシナリオで使用できますが、これらの使用シナリオにはそれぞれ独自の欠点があり、最大限に活用するには考慮しなければなりません。使用中のインデックス。
インデックスを適切に使用するには、クエリのプロファイルを作成し、インデックスに関してどのようなオプションがあるかを確認し、それらの長所と短所を理解し、要件に基づいて必要なインデックスを決定し、列にインデックスを付けた後、インデックスがMySQLで実際に使用されます。スキーマのインデックスを適切に作成すると、クエリのパフォーマンスが向上するはずですが、応答時間が満足できない場合は、改善するためにより良いインデックスを作成できるかどうかを確認してください。