高パフォーマンスMySQLの実践(3): パフォーマンスの最適化

34e84bc7db4828560fa10e6d4e31d35d.gif

この記事では主に、遅い SQL を最適化するための方法をいくつか紹介します。具体的な最適化策を説明する前に、まず EXPLAIN について説明します。クエリを分析するときに必要な操作です。その出力結果の内容を理解しておくとより有益です。 SQLを最適化します。誰もが読みやすいように、key1 のようなものがセカンダリ インデックスを表し、key_part1 が結合インデックスの最初の部分を表し、unique_key1 が一意のセカンダリ インデックスを表し、primary_key が主キー インデックスを表すことを以下に指定します。高パフォーマンス MySQL 実践 (1): テーブル構造 と高パフォーマンス MySQL 実践 (2): インデックスはこの記事の前提知識ですので、どなたでもお読みいただけます。

1.詳しく説明する

Explain は遅い SQL を最適化する前によく使用されるステートメントで、特定のクエリ プランを分析して、意図的に最適化することができます。このセクションは主に、Explain クエリ結果の各列が何に使用されるかを誰もが理解できるようにすることを目的としています。まず、各列の機能を簡単に見てみましょう。

リスト
説明する

ID

大規模なクエリ ステートメントでは、各 SELECT キーワードが一意の ID に対応します。結合クエリでは、レコードの ID 値は同じです。複数の SELECT キーワードを含むクエリでは、クエリ オプティマイザーは、複数の SELECT レコードの ID 値が同じになるようにサブクエリを最適化することがあります。

選択タイプ

クエリの種類

テーブル

テーブル名

パーティション

一致するパーティション情報

タイプ

単一テーブルへのアクセス方法

possible_keys

可能なインデックス

実際に使用されるインデックス

key_len

実際に使用されるインデックスの長さ

参照

インデックス列等価クエリを使用すると、インデックス列と一致するオブジェクト情報が等価になります。

読み取られるレコードの推定数

フィルタリングされた

推定で読み込む必要があるレコードのうち、検索条件で絞り込んだ後に残っているレコードの割合。単一テーブル クエリでは意味がありませんが、結合テーブル クエリでは、ドライバー テーブルがクエリを完了した後、駆動テーブルでクエリを実行する必要がある回数を計算できます。

余分な

その他の注意事項

これらの列のほとんどは、説明情報で十分に明確に説明されています。以下では、主にいくつかの必要な列について詳しく説明します。

1.1 選択タイプ
  • SIMPLE:クエリ文にUNION またはサブクエリを含まないクエリ

  • PRIMARY: UNION、UNION ALL、またはサブクエリを含む大規模なクエリの場合、複数の小さなクエリで構成され、一番左のクエリの select_type が PRIMARY になります。

  • UNION: UNION と UNION ALL を含む大きなクエリの場合、いくつかの小さなクエリで構成されます。左端の小さなクエリを除き、他の小さなクエリの select_type は UNION です。

  • UNION RESULT: MySQL は、UNION クエリの重複排除を完了するために一時テーブルの使用を選択します。この一時テーブルのクエリの select_type は UNION RESULT です。

  • DEPENDENT UNION: UNION クエリ関連タイプ

  • SUBQUERY、DEPENDENT SUBQUERY、MATERIALIZED: サブクエリ関連の型

  • DERIVED: 派生テーブルを含むクエリでは、クエリはマテリアライズされた派生テーブルとして実行されます。

1.2型
  • const:主キーまたは一意のセカンダリ インデックスと定数との等価比較を通じてレコードを検索します。ジョイント インデックスの場合、この const アクセスは、インデックス列の各列が定数と等価であるかどうかを比較する場合にのみ有効です。

  • 参照:セカンダリ インデックスと定数の間の同等の比較を通じて、形成されるスキャン間隔はシングルポイント スキャン間隔のアクセスになります。

  • ref_or_null: ref と比較して、NULL 値を持つさらにいくつかのセカンダリ インデックス列がスキャンされます。

  • range: インデックスを使用してクエリを実行する場合、対応するスキャン間隔は、複数のシングルポイント スキャン間隔またはレンジ スキャン間隔にアクセスします。

  • インデックス: カバーインデックスを使用し、すべてのセカンダリインデックスへのアクセスをスキャンします。さらに、フル テーブル スキャンを通じて InnoDB エンジンを使用してテーブルに対してクエリを実行するときに、 ORDER BY主キー ステートメントが追加されている場合、そのステートメントも実行時にインデックス アクセスとみなされます。

  • fulltext: フルテキスト インデックス アクセス

  • all: フルテーブルスキャン

  • eq_ref:結合クエリの実行時に、主キーまたは NULL を許可しない一意のセカンダリ インデックスを介してドリブン テーブルにアクセスする場合、等しい値の一致

外部結合では、ON ステートメントは、「ドライバー テーブルのレコードが駆動テーブルで一致するレコードを見つけられない場合、対応する駆動テーブル レコードの各フィールドが NULL で埋められる」というシナリオのために特に提案されています。内部結合では、 ON と WHERE は同じ効果があります

  • unique_subquery: IN サブクエリを含む一部のクエリ ステートメントの場合、クエリ オプティマイザーが IN サブクエリを EXISTS サブクエリに変換することを決定し、主キーまたは変換後に NULL を許可する一意のセカンダリ インデックスを使用してサブクエリを実行できる場合

  • Index_subquery: unique_subquery に似ていますが、アクセス時に通常のセカンダリ インデックスが使用される点が異なります。

  • Index_merge: インデックスのマージが存在します

  • システム: テーブルにレコードが 1 つだけあり、使用されるストレージ エンジンの統計が正確な場合 (MyISAM や MEMORY など)

1.3参照

アクセス方法が const、ref、ref_or_null、eq_ref、unique_subquery、index_subquery のいずれかの場合、ref 列にはインデックス列に相当するものが表示されます

  • const: 定数を表します

  • func: 関数を表します

  • DBName.TableName.columnName: データベースのテーブル内の列を表します。

1.4 追加事項
  • テーブルが使用されていません: クエリ ステートメントに FROM 句がありません。

  • 不可能な WHERE: クエリ ステートメントの WHERE 条件は常に FALSE です

  • 一致する最小/最大行がありません: クエリに最小または最大集計関数があるが、WHERE 条件を満たすレコードがない場合

  • インデックスの使用: カバリングインデックスの使用

  • インデックス条件の使用: インデックス条件プッシュダウン機能は、クエリ ステートメントの実行時に使用されます。

インデクス条件プッシュダウン:セカンダリインデクスのクエリ条件に最適化されており、セカンダリインデクスの条件判定時にインデクス関連列の条件を全て判定し、条件を満たした場合にテーブルの返却動作を行います。条件が満たされると、テーブルの戻りは実行されなくなり、テーブルの戻り操作の数が減り、I/O が削減されます。 

以下の例:

select * from specific_table where key1 > 'a' and key1 like '%b'; 

インデックス条件を押し下げると、key1 > 'a' を判定してからテーブルに戻るのではなく、key1 のすべての条件が判定されます。

  • 結合バッファーの使用 (ブロック ネスト ループ): 結合クエリの実行時に、駆動テーブルがインデックスを効果的に使用してアクセスを高速化することはできませんが、メモリ ブロックを使用してクエリを高速化することを示します。

  • intersect(index_name, ...) の使用、union(index_name, ...) の使用、および sort Union(index_name, ...) の使用: クエリの実行に Intersection インデックス マージ、Union インデックス マージ、または Sort-Union インデックス マージを使用することを示します。 (以下に紹介文があります)

  • ファイルソートの使用: ファイルのソート、ソートはインデックスを使用できず、メモリまたはディスク内でのみソートできます。

  • 一時テーブルの使用: クエリ中に内部一時テーブルが使用されます。

2. 最適化に関する考慮事項

アクセスタイプに基づいて最適化する

前回の記事では、EXPLAIN文のアクセスタイプ(type)について詳しく紹介しましたが、クエリのアクセスタイプが期待したものと異なる場合、最も簡単で直接的な解決策は、検索条件に適切なインデックスを追加することです。コラム

走査線数を削減する最適化

場合によっては、次の SQL を実行するなど、インデックスを追加するだけでは問題が解決しないことがあります。

 
  
select name, count(name) from specific_table group by key1;

この SQL の実行後、返されるデータは数行のみですが、COUNT 集計関数があるため、テーブル内のデータの総量に応じて、スキャンする必要があるデータが数千行になる場合があります。大量のデータがスキャンされても数行しか返されないこの種の状況では、通常、別の概要テーブルを追加することで最適化できます。もちろん、これには、データを維持するためにアプリケーション層に対応するロジックを追加する必要があります。概要表。

さらに、複雑なクエリを書き換えることによって最適化することもできます。クエリを書き換える際に考慮する必要がある方向を以下に紹介します。

1 つの複雑なクエリですか、それとも複数の単純なクエリですか?

これは検討する価値のある質問です。複雑なクエリを複数の単純なクエリに分割してデータベースの作業を可能な限り削減し、一部の処理ロジックをアプリケーション層に移動します。MySQL は単純なクエリを非常に効率的に処理するため、そうすることで通常は効率が向上します。

セグメンテーション

実際の業務では、大量のデータを含むデータベーステーブルを繰り越す(または削除する)場合通常、大きなクエリを小さなクエリに分割するセグメンテーション手法が使用されます。各クエリの機能は同じですが、操作量が異なります。小さなクエリが実行されるたびに、大きなクエリのタスクが完了します。

一度に大量のデータを転送すると、大量のデータがロックされ、トランザクション ログ全体が占有され、システム リソースが枯渇し、多くの小さなクエリがブロックされる可能性があります。この状況を回避するために、通常、データ転送では10,000 アイテムのみが操作されます。これにより、サーバーへの影響が最小限に抑えられ、転送が完了するたびに、次のタスクを実行する前にしばらく一時停止することができます。これにより、負荷が長期間にわたって分散され、サーバーへの影響が大幅に軽減されます。サーバーにアクセスし、ロックが保持される時間を短縮します。

結合クエリを最適化する

結合テーブルが多すぎる場合は、複数のクエリまたは複数の単一テーブル クエリに分割する必要があります (単一テーブル クエリの方がキャッシュ効率が高くなります) クエリが分解されると、クエリ間のロック競合が減少します。 。また、ジョイントテーブルをクエリする場合は、次の 2 つの点に注意する必要があります。

  • ON 句または USING 句の列にインデックスがあることを確認してください。

  • MySQL がインデックスを使用してクエリを最適化できるように、GROUP BY および ORDER BY の式は 1 つのテーブル内の列のみを参照するようにしてください。

IN()条件とOR条件

一般に、IN() は複数の OR 条件と完全に同等であると考えられますが、MySQL ではこの 2 つには違いがあります。MySQL が IN() 条件を処理するときは、まずリスト内のデータを並べ替えてから、バイナリ検索を使用してリスト内の値が条件を満たすかどうかを判断します。これは、時間計算量が O(logn) の操作です。等価的に OR クエリに変換すると、その時間計算量は O(n) となるため、IN() 条件に多数の値がある場合、MySQL の処理が高速になります。

クエリ中にインデックスが無効かどうか
  • インデックスの左端の列から検索が開始されない場合、インデックスは使用できません。

  • 共用体インデックス内の列がスキップされると、そのインデックスは使用できなくなるか、部分インデックスのみが使用できるようになります。次の SQL があります。ここで、key_part1、key_part2、key_part3 は順番に結合インデックスです。

 
  
select key_part1, key_part2, key_part3 from specific_table
where key_part1 = 1 and key_part3 = 3;

問合せ条件でkey_part2を省略した場合はインデクスの先頭列のみ使用可能となり、key_part1を省略した場合は結合インデクスは使用できません。

  • クエリ内の列に範囲クエリがある場合、その右側にあるすべての列はインデックス最適化を使用してクエリや並べ替えを行うことができません。この場合、範囲クエリ列の値の数が制限されている場合、OR 接続を使用して範囲クエリを複数の等価一致に置き換えることができます

  • 検索条件の列名が列名として単独で出現せず、式や関数を使用している場合、インデクスは使用できません。次のSQLのように、key1列はkey1という形式で出現します。 ※2 インデックスは使用されません。

 
  
select * from specific_table where key1 * 2 > 4;
  • 可変長フィールドに対して % で始まるファジー クエリを使用する場合、インデックスは使用されません。MySQL は文字列を 1 文字ずつソートするので、これは理解しやすいですが、先頭に % を使用すると、比較を完了できず、フル テーブル スキャンのみを使用できます。

ソート時にインデックスが無効かどうか
  • ORDER BY文以降の列の順序が結合インデックスの列順序と異なる場合、そのインデックスは使用できません。

  • ASCとDESCが混在している場合、インデックスは使用できません。

以下のようなSQLがありますが、key_part1、key_part2の順に結合インデックスとなっており、実行時にはインデックスを使用できません。

 
  
select key_part1, key_part2 from specific_table 
order by key_part1, key_part2 desc;

MySQL バージョン 8.0 では、ASC インデックスと DESC インデックスの混合使用をサポートできます。

  • 次の SQL に示すように、ソート列に同じインデックスではない列が含まれている場合、そのインデックスは使用できません。

 
  
select id, key1, key2 from specific_table order by key1, key2;

これらは同じインデックスではないため、key1 が同じ場合、key2 列に従ってソートされないため、インデックスは使用されません。

  • ソート列が結合インデックスのインデックス列であっても、これらのソート列が結合インデックス内で連続していない場合、そのインデックスは使用できません。以下のSQLのように、結合インデクスはkey_part1でソート後、key_part3でソートされていないため、インデクスは使用できません。

 
  
select key_part1, key_part3 
from specific_table 
order by key_part1, key_part3;
  • ソート列が ORDER BY ステートメント内で別の列名として表示されない場合、インデックスは使用できません。以下のSQLのようにソート時に関数を使用するためインデックスは使用できません。

 
  
select id, key1, key2 from specific_table order by upper(key1)
空ではないインデックス列の最適化

Min() および Max() 操作が必要な場合、null ではないインデックス列を使用すると効率が向上します。たとえば、特定の列の最小値を見つけるには、対応する B ツリー インデックスの左端のレコードをクエリするだけで済みます。クエリ オプティマイザーはこの式を定数として扱います。 ESPLAIN 結果の Extra 列。離れています。」

重複した冗長なインデックス

次の SQL に示すように、重複インデックスは、同じ列に同じ順序で作成された同じタイプのインデックスを指します。

 
  
create table specific_table (
    id int not null primary key,
    unique key(id)
)engine=InnoDB;

id 列に 2 つの同一のインデックスが作成されるため、一意のインデックスを削除する必要があります。

冗長インデックスは通常、既存のインデックス (column_a) にインデックス (column_a、column_b) を追加するなど、テーブルに新しいインデックスを追加するときに発生します。これは、2 番目の結合インデックスが同じものを実行できるため、冗長インデックスが発生する状況です。単一列のインデックスとしての役割。

ほとんどの場合、冗長なインデックスは必要ないため、新しいインデックスを作成するのではなく、既存のインデックスを拡張するように努めるべきです。

インデックスのマージはありますか?

複数の単一列インデックスを複数の列に個別に作成しても、ほとんどの場合、MySQL クエリのパフォーマンスは向上しません。

MySQL には「インデックス マージ」戦略があり、テーブル内の複数の単一列インデックスを使用して、指定されたデータ行を検索し、スキャン結果をマージできます。インデックスのマージ戦略は非常に優れている場合もありますが、多くの場合、テーブル内のインデックスの構築が不十分であることがわかります。

  • クエリ オプティマイザーが複数のインデックスをマージする必要がある場合、通常は、複数の独立した単一列インデックスではなく、関連するすべての列を含むユニオン インデックスを意味します。

  • オプティマイザーが複数のインデックスをマージする必要がある場合、特に一部のインデックス列値の選択性が高くなく、大量のデータのスキャンリターンをマージする必要がある

  • オプティマイザはこれらの操作をクエリ コストにカウントしないため、クエリ コストが「過小評価」され、実行計画がフル テーブル スキャンよりも悪くなります。

一般に、インデックスを再構築するか、UNION を使用してクエリを書き直すことを検討する必要があります。さらに、次の SQL に示すように、optimizer_switch パラメーターを変更することで、インデックスのマージ機能をオフにすることができます。

 
  
SELECT @@optimizer_switch;


-- 改成 index_merge=off 
set optimizer_switch = 'index_merge=off, ...';

IGNORE INDEX 構文を使用して、オプティマイザに特定のインデックスを無視させることもできます。これにより、オプティマイザがそのインデックスを含むインデックスを使用して実行プランをマージするのを防ぐことができます。

 
  
select * from specific_table ignore index(index_name)
where column_name = #{value};

インデックスのマージが発生したときにインデックスを無視することを考慮するだけでなく、クエリの実行時に適切なスキャン間隔を形成できず、スキャン レコードの数を減らすことができない場合は、インデックスを無視して全テーブル スキャンを使用することも考慮する必要があります。達成される。

以下では、誰もがインデックスのマージをより完全に理解できるように、Intersection インデックスのマージ、Union インデックスのマージ、および Sort-Union インデックスのマージの 3 つのタイプのインデックス マージを紹介します。

交差点インデックスのマージ

次のクエリを見てみましょう。

select * from specific_table where key1 = 'a' and key2 = 'b';

ご存知のことは、インデックス列の値が同じ場合、セカンダリ インデックス レコードは主キー値のサイズに従って並べ替えられ、主キー値は key1 でフィルターされ、主キー値は key1 でフィルターされます。 key2 は交差することができます。そして、その結果に基づいてテーブルを返す操作を実行します。これは、key1 と key2 に対してそれぞれフィルタリングされた主キー値を返すよりもコストが低くなります。この場合、交差インデックスのマージ戦略が使用されます。

Union 索引合并

次のクエリを見てみましょう。

 
  
select * from specific_table where key1 = 'a' or key2 = 'b';
key1 でフィルタリングされた主キー値と key2 でフィルタリングされた主キー値の和集合を取得し、その結果に基づいてテーブルを返す操作を実行します。このアプローチはユニオン インデックスのマージと呼ばれ、比較される場合がありますフルテーブルスキャンを直接実行するため、オーバーヘッドは低いはずです。Union インデックスのマージでは、セカンダリ インデックスによってフィルタリングされた主キーの値が順序どおりである必要があることに注意してください。主キーの値が順序どおりでない場合は、Sort-Union インデックスのマージを検討する必要があります。
ソートとユニオンのインデックスのマージ

次のようなクエリがあります。

 
  
select * from specific_table where key1 < 'a' or key2 > 'b';

上記のクエリ条件をレンジクエリ条件に変更しました 各インデックスで絞り出された主キーの値の順序が崩れたため、Unionインデックスの結合が利用できなくなりました Unionインデックスの結合をベースにSort-Unionインデックスの結合を追加しました. 並べ替え操作: key1 でフィルターされた主キー値と key2 でフィルターされた主キー値を並べ替え、マージに引き続き Union インデックスを使用できるようにします。

COUNT()を最適化する

結果を value でカウントする必要がある場合は、 COUNT() 条件で列名または COUNT(0) を指定する必要があります。すべての行をカウントする必要がある場合は、すべての列を無視する COUNT(*) を指定する必要があります。すべての行を直接カウントします。この 2 点を理解すると、データ統計を行う際に意図をより明確に伝えることができます。

一般に、COUNT() クエリは正確な結果を得るために多数のデータ行をスキャンする必要があるため、最適化が困難です。ビジネス シナリオで完全な精度が必要ない場合は、代わりにEXPLAIN を使用して行数を推定するか、クエリ条件の一部の制約を削除して DISTINCT を削除して並べ替え操作を回避できます。これらの実践により、統計クエリのパフォーマンスが向上する可能性があります。

UNION クエリの最適化

UNION クエリを使用する場合、重複行を削除する必要がない場合は、UNION ALL を使用する必要があります。ALL キーワードがない場合、MySQL は一時テーブルに DISTINCT を追加し、データの重複を排除し、コストが比較的高くなるからです。 。さらに、WHERE、LIMIT、ORDER BY ステートメントを各クエリに適用できるため、MySQL がクエリをより適切に最適化できます。

オフセットの最適化

ページング クエリでは、OFFSET により MySQL が多数の不要な行をスキャンし、それらを破棄します。たとえば、式 LIMIT 1000, 20 は 1020 個のデータをクエリし、最初の 1000 個を破棄します。これは非常にコストがかかります。

ブックマークを使用して、データが最後に読み取られた「位置」を記録できるため、次のクエリは OFFSET の使用を避けて、その位置から直接スキャンを開始できます。たとえば、各ページに 20 個のデータが表示され、現在のページのデータ ID 値が 200 であると記録します。次に、次のページのデータを見ると、クエリ SQL は次のようになります。

 
  
select * from specific_table
where id <= 180
limit 20;

ただし、この状況には欠点もあり、ページ番号を指定してクエリを実行することはできず、たとえば、5 ページ目のデータを見たい場合、対応するページの特定の ID 値の範囲を計算することができません。ID 値が単調増加しており、データが削除されていないことが確認できなければ、この場合、ID 値は連続しているため、5 ページのデータの ID 値が 120 から始まることは簡単に計算できます。この利点は、ページをどれだけ後ろにめくっても非常にうまく機能することです。

GROUP BY を最適化するには WITH ROLLUP を使用します

通常、グループ集計クエリを実行するには GROUP BY を使用します。グループ化された結果を再度合計したい場合は、WITH ROLLUP 操作を使用できますが、より良い方法は、WITH ROLLUP 処理をアプリケーション層に取り込むことです。

テーブルの最適化

大量のデータを削除したり、データを挿入するときに主キーの昇順に挿入されない場合、大量のメモリの断片が生成され、データ クエリの効率に影響を与える可能性があります。これは、データが削除されると、MySQL はデータをすぐにクリアしてスペースを整理するのではなく、削除対象としてマークするためです。OPTIMIZE TABLE を使用すると、スペースを整理し、メモリの断片化を減らすことができます。

InnoDB エンジンは OPTIMIZE TABLE 操作をサポートしていないため、次のメッセージが表示されます。

 
  
OPTIMIZE TABLE specific_table;


-- Table does not support optimize, doing recreate + analyze instead

何も操作を行わずに、ALTER コマンドを使用して上記の目的の式を再構築できます。

 
  
alter table specific_table engine=InnoDB;

実行完了後、以下のSQLで実行状況を確認し、data_free列が0であれば領域のデフラグが成功したことを意味します。

 
  
show table status from specific_db like specific_table;

ただし、ほとんどの場合、これは必要ありません。

破損したテーブルを見つけて修復する

ハードウェアの問題、MySQL 自体の欠陥、またはオペレーティング システムの問題により、インデックスが破損している可能性があります。もちろん、この問題は非常にまれです。ほとんどのテーブルとインデックスのエラーは、次の SQL で確認できます。

 
  
check table specific_table;

例外が見つかった場合は、次の SQL を使用して修復できます。

 
  
repair table specific_table;


-- 如果存储引擎不支持上述操作的话,也可通过表重建来完成
alter table specific_table engine=InnoDB;

参考文献:

[1] 「ハイパフォーマンス MySQL 第 4 版」: 第 7 章および第 8 章

[2] 「MySQL の仕組み」: 第 7 章、第 10 章、第 11 章、第 14 章、および第 15 章

[3] MySQL:optimizer_switch

[4] 8.9.4 インデックスのヒント

[5] MySQL の上級: テーブルの最適化コマンド

-終わり-

おすすめ

転載: blog.csdn.net/jdcdev_/article/details/133565446