5. ソートの最適化
5.1 ソートの最適化
質問: WHERE 条件フィールドにインデックスを追加しますが、ORDER BY フィールドにインデックスを追加する必要があるのはなぜですか?
最適化の提案:
-
SQL では、WHERE 句と ORDER BY 句でインデックスを使用して、WHERE 句での完全なテーブル スキャンと ORDER BY 句での FileSort の並べ替えを回避できます。もちろん、場合によっては、テーブル全体のスキャンや FileSort の並べ替えが必ずしもインデックス作成より遅いとは限りません。ただし、一般に、クエリの効率を向上させるためには、依然としてこれを回避する必要があります。
-
Index を使用して ORDER BY 並べ替えを完了してみてください。WHERE と ORDER BY の後に同じ列が続く場合は、単一のインデックス列を使用し、それらが異なる場合は、結合インデックスを使用します。
-
Index が使用できない場合は、FileSort メソッドのチューニングが必要です。
INDEX a_b_c(a,b,c)
order by 能使用索引最左前缀
- ORDER BY a
- ORDER BY a,b
- ORDER BY a,b,c
- ORDER BY a DESC,b DESC,c DESC
如果WHERE使用索引的最左前缀定义为常量,则order by 能使用索引
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b = const ORDER BY c
- WHERE a = const ORDER BY b,c
- WHERE a = const AND b > const ORDER BY b,c
不能使用索引进行排序
- ORDER BY a ASC,b DESC,c DESC /* 排序不一致 */
- WHERE g = const ORDER BY b,c /*丢失a索引*/
- WHERE a = const ORDER BY c /*丢失b索引*/
- WHERE a = const ORDER BY a,d /*d不是索引的一部分*/
- WHERE a in (...) ORDER BY b,c /*对于排序来说,多个相等条件也是范围查询*/
5.2 ケースの実践
ORDER BY 句では、インデックス ソートを使用し、FileSort ソートの使用を避けてください。
ケースを実行する前に、学生のインデックスをクリアして、主キーのみを残します。
DROP INDEX idx_age ON student;
DROP INDEX idx_age_classid_stuno ON student;
DROP INDEX idx_age_classid_name ON student;
#或者
call proc_drop_index('atguigudb2','student');
シナリオ: 30 歳で学生番号が 101000 未満の学生をユーザー名で並べ替えてクエリします。
mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY
-> NAME ;
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-----------------------------+
| 1 | SIMPLE | student | NULL | ALL | NULL | NULL | NULL | NULL | 498917 | 3.33 | Using where; Using filesort |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-----------------------------+
1 row in set, 2 warnings (0.00 sec)
クエリ結果は次のとおりです。
mysql> SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY
-> NAME ;
+-----+--------+--------+------+---------+
| id | stuno | name | age | classId |
+-----+--------+--------+------+---------+
| 695 | 100695 | bXLNEI | 30 | 979 |
| 322 | 100322 | CeOJNY | 30 | 40 |
| 993 | 100993 | DVVPnT | 30 | 340 |
| 983 | 100983 | fmUNei | 30 | 433 |
| 946 | 100946 | iSPxRQ | 30 | 511 |
| 469 | 100469 | LTktoo | 30 | 69 |
| 45 | 100045 | mBZrKC | 30 | 280 |
| 635 | 100635 | nQnUJL | 30 | 732 |
| 16 | 100016 | NzjxKh | 30 | 539 |
| 363 | 100363 | OMuKtM | 30 | 695 |
| 293 | 100293 | qOYywO | 30 | 586 |
| 169 | 100169 | qUElsg | 30 | 526 |
| 798 | 100798 | rhHPdX | 30 | 71 |
| 749 | 100749 | TCgaJe | 30 | 697 |
| 157 | 100157 | TUQtvY | 30 | 22 |
| 580 | 100580 | UHDUOj | 30 | 423 |
| 532 | 100532 | XvmZkc | 30 | 861 |
| 939 | 100939 | yBlCbB | 30 | 320 |
| 710 | 100710 | yhmRvD | 30 | 219 |
| 266 | 100266 | YueogP | 30 | 524 |
+-----+--------+--------+------+---------+
20 rows in set, 1 warning (0.16 sec)
結論: type は ALL で、これは最悪のケースです。ファイルソートの使用はエクストラにも表示されますが、これも最悪のシナリオです。最適化は必須です。
最適化のアイデア:
オプション 1: ファイルソートを削除するには、インデックスを構築します。
#创建新索引
CREATE INDEX idx_age_name ON student(age,NAME);
オプション 2: フィルター条件と並べ替えに上位インデックスを使用してみてください。
3 つのフィールドを組み合わせたインデックスを作成します。
DROP INDEX idx_age_name ON student;
CREATE INDEX idx_age_stuno_name ON student (age,stuno,NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY NAME;
mysql> EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 30 AND stuno <101000 ORDER BY NAME;
+----+-------------+---------+------------+-------+--------------------+--------------------+---------+------+------+----------+---------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+--------------------+--------------------+---------+------+------+----------+---------------------------------------+
| 1 | SIMPLE | student | NULL | range | idx_age_stuno_name | idx_age_stuno_name | 9 | NULL | 20 | 100.00 | Using index condition; Using filesort |
+----+-------------+---------+------------+-------+--------------------+--------------------+---------+------+------+----------+---------------------------------------+
1 row in set, 2 warnings (0.00 sec)
mysql> SELECT SQL_NO_CACHE * FROM student
-> WHERE age = 30 AND stuno <101000 ORDER BY NAME ;
+-----+--------+--------+------+---------+
| id | stuno | name | age | classId |
+-----+--------+--------+------+---------+
| 695 | 100695 | bXLNEI | 30 | 979 |
| 322 | 100322 | CeOJNY | 30 | 40 |
| 993 | 100993 | DVVPnT | 30 | 340 |
| 983 | 100983 | fmUNei | 30 | 433 |
| 946 | 100946 | iSPxRQ | 30 | 511 |
| 469 | 100469 | LTktoo | 30 | 69 |
| 45 | 100045 | mBZrKC | 30 | 280 |
| 635 | 100635 | nQnUJL | 30 | 732 |
| 16 | 100016 | NzjxKh | 30 | 539 |
| 363 | 100363 | OMuKtM | 30 | 695 |
| 293 | 100293 | qOYywO | 30 | 586 |
| 169 | 100169 | qUElsg | 30 | 526 |
| 798 | 100798 | rhHPdX | 30 | 71 |
| 749 | 100749 | TCgaJe | 30 | 697 |
| 157 | 100157 | TUQtvY | 30 | 22 |
| 580 | 100580 | UHDUOj | 30 | 423 |
| 532 | 100532 | XvmZkc | 30 | 861 |
| 939 | 100939 | yBlCbB | 30 | 320 |
| 710 | 100710 | yhmRvD | 30 | 219 |
| 266 | 100266 | YueogP | 30 | 524 |
+-----+--------+--------+------+---------+
20 rows in set, 1 warning (0.00 sec)
その結果、filesort の SQL の実行速度は、filesort の最適化された SQL の実行速度を上回り、はるかに高速になり、結果はほぼ瞬時に表示されました。
結論は:
- 2 つのインデックスが同時に存在し、MySQL が自動的に最適なソリューションを選択します。(この例では、mysql は idx_age_stuno_name を選択します)。ただし、データ量が変化すると、選択されるインデックスも変化します。
- [範囲条件] フィールドと [グループ化または並べ替え] フィールドのいずれかを選択する場合、フィルタリングされたデータが十分にあり、並べ替える必要があるデータがそれほど多くない場合は、条件フィールドのフィルタリング量を観察することが優先されます。の場合、フィールドの範囲内にインデックスを配置することが優先されます。逆に。
5.3 ファイルソートアルゴリズム: 双方向ソートと一方向ソート
双方向ソート (遅い)
-
MySQL 4.1 より前では、双方向ソートが使用されていました。これは、文字通りディスクを 2 回スキャンして最終的にデータを取得し、行ポインタを読み取って列ごとに並べ替え、それらをソートし、ソートされたリストをスキャンして、そのリストから再開することを意味します。リスト内の値に対応するリストから出力された対応するデータを読み取ります
-
ディスクから並べ替えフィールドを取得し、バッファー内で並べ替えてから、ディスクから他のフィールドを取得します。
データのバッチを取得するには、ディスクを 2 回スキャンする必要があります。ご存知のとおり、IO には非常に時間がかかるため、mysql4.1 以降、2 番目の改良されたアルゴリズムである単方向ソートが登場しました。
一方向ソート (高速)
クエリに必要なすべての列をディスクから読み取り、列ごとの順序に従ってバッファ内で並べ替えてから、並べ替えられたリストをスキャンして出力します。これはより効率的であり、データの 2 回目の読み取りを回避します。また、ランダム IO をシーケンシャル IO に変換しますが、各行をメモリに保存するため、より多くのスペースを使用します。
結論と提起された疑問
- シングル パスは後ろから出てくるため、デュアル パスよりも一般的に優れています。
- ただし、単一チャネルの使用には問題があります
6. GROUP BY の最適化
- Index by by の利用原理は order by とほぼ同じで、Group by ではインデックスを利用したフィルター条件がなくてもインデックスを直接利用することができます。
- group by インデックス構築のための最も左の接頭辞ルールに従って、最初にソートし、次にグループ化します。
- インデックス列を使用できない場合は、max_length_for_sort_data パラメーターと sort_buffer_size パラメーターの設定を増やします。
- haveよりwhereの方が効率が良いので、whereに条件が書ける場合はhavingに書かないでください。
- order by の使用を減らし、ソートせずにビジネスと通信するか、ソートをプログラムに組み込みます。Order by、groupby、distinct などのステートメントはより多くの CPU を消費するため、データベースの CPU リソースは非常に貴重です。
- order by、group by、distinct などのクエリ ステートメントが含まれます。where 条件でフィルタリングされた結果セットは 1,000 行以内に収める必要があり、そうしないと SQL が非常に遅くなります。
7. ページングクエリを最適化する
最適化アイデア 1
インデックスの並べ替えとページング操作を完了し、最後に主キーに基づいて元のテーブル クエリで必要な他の列の内容にインデックスを関連付けます。
mysql> EXPLAIN SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10)
-> a
-> WHERE t.id = a.id;
+----+-------------+------------+------------+--------+---------------+---------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+------+--------+----------+-------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 498917 | 100.00 | NULL |
| 1 | PRIMARY | t | NULL | eq_ref | PRIMARY | PRIMARY | 4 | a.id | 1 | 100.00 | NULL |
| 2 | DERIVED | student | NULL | index | NULL | PRIMARY | 4 | NULL | 498917 | 100.00 | Using index |
+----+-------------+------------+------------+--------+---------------+---------+---------+------+--------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)
最適化アイデア 2
このソリューションは、自動インクリメント主キーを持つテーブルに適しており、特定の場所で Limit クエリをクエリに変換できます。
mysql> EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | student | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 1 | 100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
8. カバーインデックスの優先順位付け
8.1 カバリングインデックスとは何ですか?
方法 1 を理解する: インデックスは行を効率的に検索する方法ですが、一般的なデータベースではインデックスを使用して列内のデータを検索することもできるため、行全体を読み取る必要はありません。結局のところ、インデックス リーフ ノードにはインデックス付けされたデータが格納され、インデックスを読み取ることで目的のデータが取得できる場合は、行を読み取る必要はありません。クエリ結果を満たすデータを含むインデックスをカバーインデックスと呼びます。
方法 2 を理解する: 非クラスター化複合インデックスの形式。クエリ内の SELECT、JOIN、および WHERE 句で使用されるすべての列が含まれます (つまり、インデックスの構築に使用されるフィールドは、クエリ条件に関係するフィールドとまったく同じです)
。 。簡単に言うと、インデックス列 + 主キーには、SELECT から FROM までクエリされた列が含まれます。
8.2 インデックスをカバーすることの長所と短所
利点:
-
Innodb テーブル インデックスの二次クエリを避ける (テーブル リターン)
-
ランダム IO をシーケンシャル IO に変換してクエリ効率を向上させることができます
欠点:
インデックス フィールドのメンテナンスには常にコストがかかります。したがって、カバーインデックスをサポートするために冗長インデックスを構築する際には、考慮すべきトレードオフがあります。これはビジネス DBA、つまりビジネス データ アーキテクトの仕事です。