MySQLがインデックスを使用することを選択するために、選択されたすべての列にインデックスを付ける必要がありますか
インデックスを使用する価値があるかどうかを決定する要因があるため、これはロードされた質問です。
要因#1
特定のインデックスについて、重要な母集団は何ですか?言い換えると、インデックスに記録されているすべてのタプルのカーディナリティ(明確なカウント)は何ですか?
要因#2
どのストレージエンジンを使用していますか?必要なすべての列にインデックスからアクセスできますか?
次は何ですか ???
簡単な例を見てみましょう:2つの値(男性と女性)を保持するテーブル
インデックスの使用状況をテストして、このようなテーブルを作成しましょう
USE test
DROP TABLE IF EXISTS mf;
CREATE TABLE mf
(
id int not null auto_increment,
gender char(1),
primary key (id),
key (gender)
) ENGINE=InnODB;
INSERT INTO mf (gender) VALUES
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
ANALYZE TABLE mf;
EXPLAIN SELECT gender FROM mf WHERE gender='F';
EXPLAIN SELECT gender FROM mf WHERE gender='M';
EXPLAIN SELECT id FROM mf WHERE gender='F';
EXPLAIN SELECT id FROM mf WHERE gender='M';
InnoDBのテスト
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.07 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.06 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql>
MyISAMをテストする
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.00 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 36 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | mf | ALL | gender | NULL | NULL | NULL | 40 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql>
InnoDBの分析
データがInnoDBとしてロードされたとき、4つのEXPLAIN
プランすべてがgender
インデックスを使用したことに注意してください。要求されたデータがであったとしても、3番目と4番目のEXPLAIN
計画はgender
インデックスを使用しましたid
。どうして?ためid
でありPRIMARY KEY
、すべてのセカンダリインデックスの参照ポインタがバックアップにしているPRIMARY KEY
(介しgen_clust_index)。
MyISAMの分析
データがMyISAMとしてロードされたとき、最初の3つのEXPLAIN
プランがgender
インデックスを使用したことに注意してください。4番目のEXPLAIN
計画では、クエリオプティマイザーはインデックスをまったく使用しないことを決定しました。代わりに全表スキャンを選択しました。どうして?
DBMSに関係なく、クエリオプティマイザーは非常に単純な経験則に基づいて動作します:インデックスがルックアップの実行に使用される候補としてスクリーニングされている場合、クエリオプティマイザーは合計数の5%以上をルックアップする必要があると計算しますテーブルの行:
- 取得に必要なすべての列が選択したインデックスにある場合、フルインデックススキャンが実行されます
- それ以外の場合は全表スキャン
結論
適切なカバリングインデックスがない場合、または特定のタプルのキー人口がテーブルの5%を超える場合、6つのことが発生する必要があります。
- クエリのプロファイルを作成する必要があることに気付きます
- すべての検索
WHERE
、GROUP BY
これらのクエリから、およびORDER BY`句を
- この順序でインデックスを作成する
WHERE
静的な値を持つ句の列
GROUP BY
列
ORDER BY
列
- 全表スキャンを回避する(賢明な
WHERE
句のないクエリ)
- 不正なキー集団の回避(または少なくともこれらの不正なキー集団のキャッシュ)
- テーブルに最適なMySQLストレージエンジン(InnoDBまたはMyISAM)を決定する
私は過去にこの5%の経験則について書きました。
更新2012-11-14 13:05 EDT
あなたの質問と元のSO投稿を振り返った。それから、Analysis for InnoDB
前に言った私のことを考えました。person
テーブルと一致します。どうして?
テーブルmf
とperson
- ストレージエンジンはInnoDBです
- 主キーは
id
- テーブルアクセスはセカンダリインデックスによる
- テーブルがMyISAMの場合、まったく異なる
EXPLAIN
計画が表示されます
ここで、SO質問からのクエリを見てくださいselect * from person order by age\G
。WHERE
句がないため、明示的に全表スキャンを要求しました。テーブルのデフォルトのソート順は、id
auto_incrementのために(PRIMARY KEY)であり、gen_clust_index(別名Clustered Index)は内部ROWIDによって順序付けられます。インデックスで注文した場合、InnoDBセカンダリインデックスには各インデックスエントリに添付されたROWIDがあることに注意してください。これにより、毎回完全な行アクセスが内部的に必要になります。
ORDER BY
InnoDBインデックスの編成方法に関するこれらの事実を無視すると、InnoDBテーブルでのセットアップはかなり困難な作業になる可能性があります。
そのSOクエリに戻ると、完全なテーブルスキャンが明示的に要求されたため、MySQLクエリオプティマイザーが正しいことを行いました(または、少なくとも、最も抵抗の少ないパスを選択しました)。InnoDBとSOクエリに関してはfilesort
、各セカンダリインデックスエントリに対してgen_clust_indexを介したフルインデックススキャンと行ルックアップを実行するよりも、テーブル全体をスキャンしてからいくつかを実行する方がはるかに簡単です。
EXPLAINプランを無視するため、Index Hintsの使用を推奨していません。それにもかかわらず、InnoDBよりもデータを実際によく知っている場合は、特にWHERE
句がないクエリでは、インデックスヒントに頼る必要があります。
更新2012-11-14 14:21 EDT
書籍MySQL Internalsの理解によると
202ページ7項は次のように述べています。
データはクラスター化インデックスと呼ばれる特別な構造に格納されます。クラスター化インデックスは、キー値として機能する主キーとデータ部分の実際のレコード(ポインターではなく)を持つBツリーです。したがって、各InnoDBテーブルには主キーが必要です。指定されていない場合、通常はユーザーに表示されない特別な行ID列が追加され、主キーとして機能します。セカンダリキーには、レコードを識別するプライマリキーの値が格納されます。Bツリーコードはinnobase / btr / btr0btr.cにあります。
これが私が以前に述べた理由です:各セカンダリインデックスエントリに対してgen_clust_indexを介したフルインデックススキャンと行ルックアップを行うよりも、フルテーブルスキャンを実行してからファイルソートを実行する方がはるかに簡単です。InnoDBは毎回二重インデックス検索を実行します。それはある種残忍なように聞こえますが、それは事実です。繰り返しますが、WHERE
条項の欠如を考慮してください。これ自体は、MySQL Query Optimizerが全テーブルスキャンを実行するためのヒントです。
FOR ORDER BY
(この質問の特定のケースです)。質問では、この場合、ストレージエンジンはそうであると述べていましたInnoDB
(そして、元のSOの質問は、10,000行が8つのアイテムにかなり均一に分散されていることを示しています。悲しいことに、私はこれが質問に答えるとは思わない。