MySQLクエリのパフォーマンスに影響を与える大きなオフセットの理由と最適化の詳細な説明

MySQLクエリのパフォーマンスに影響を与える大きなオフセットの理由と最適化の詳細な説明

序文

MySQL クエリは select コマンドを使用し、limit および offset パラメータを使用して、指定された範囲のレコードを読み取ることができます。この記事では、オフセットが大きすぎて MySQL クエリのパフォーマンスに影響する理由と、それを最適化する方法について紹介します。

テストデータシートとデータを準備する

1. テーブルを作成する

テーブル「メンバー」を作成(
 `id` int(10) 符号なし NOT NULL AUTO_INCREMENT,
 `name` varchar(10) NOT NULL COMMENT '名前',
 `gender` tinyint(3) unsigned NOT NULL COMMENT '性別',
 主キー (`id`)、
 キー `gender` (`gender`)
)ENGINE=InnoDB デフォルト文字セット=utf8;

2. 1,000,000件のレコードを挿入する

<?php
$pdo = 新しい PDO ("mysql:host=localhost;dbname=user","root",'');

($i=0; $i<1000000; $i++){
 $name = substr(md5(time().mt_rand(000,999)),0,10);
 性別 = mt_rand(1,2);
 $sqlstr = "メンバー(名前、性別)に値('".$name."','".$gender."')を挿入します";
 $stmt = $pdo->prepare($sqlstr);
 $stmt->execute();
}
?>

mysql> メンバーから count(*) を選択します。
+----------+
| カウント(*) |
+----------+
| 1000000 |
+----------+
セット内1列(0.23秒)

3. 現在のデータベースバージョン

mysql> バージョンを選択します();
+-----------+
| バージョン() |
+-----------+
| 5.6.24 |
+-----------+
セット内の1行(0.01秒)

大きなオフセットがパフォーマンスに影響を与える理由を分析する

1. オフセットが小さい場合

mysql> メンバーから * を選択し、性別 = 1 を制限 10,1 にします。
+----+------------+--------+
| ID | 名前 | 性別 |
+----+------------+--------+
| 26 | 509e279687 | 1 |
+----+------------+--------+
セット内の 1 行 (0.00 秒)

mysql> メンバーから * を選択して、性別 = 1 の制限を 100,1 にします。
+-----+------------+--------+
| ID | 名前 | 性別 |
+-----+------------+--------+
| 211 | 07c4cbca3a | 1 |
+-----+------------+--------+
セット内の 1 行 (0.00 秒)

mysql> メンバーから * を選択して、性別 = 1 の制限を 1000,1 にします。
+------+------------+---------+
| ID | 名前 | 性別 |
+------+------------+---------+
| 1975 | e95b8b6ca1 | 1 |
+------+------------+---------+
セット内の 1 行 (0.00 秒)

オフセットが小さいとクエリ速度が速くなり、効率が高くなります。

2. 大きなオフセット

mysql> メンバーから * を選択して、性別 = 1 の制限を 100000,1 にします。
+--------+------------+---------+
| ID | 名前 | 性別 |
+--------+------------+---------+
| 199798 | 540db8c5bc | 1 |
+--------+------------+---------+
セット内1列(0.12秒)

mysql> メンバーから * を選択し、性別 = 1 を制限 200000,1 にします。
+--------+------------+---------+
| ID | 名前 | 性別 |
+--------+------------+---------+
| 399649 | 0b21fec4c6 | 1 |
+--------+------------+---------+
セット内1列(0.23秒)

mysql> メンバーから * を選択して、性別 = 1 の制限を 300000,1 にします。
+--------+------------+---------+
| ID | 名前 | 性別 |
+--------+------------+---------+
| 599465 | f48375bdb8 | 1 |
+--------+------------+---------+
セット内1列(0.31秒)

オフセットが大きい場合、効率の問題が発生し、オフセットが増加するにつれて、実行効率が低下します。

パフォーマンスに影響を与える理由を分析する

性別=1 の制限 300000,1 のメンバーから * を選択します。

データ テーブルは InnoDB であるため、InnoDB インデックスの構造に応じて、クエリ プロセスは次のようになります。

  • セカンダリ インデックスを通じて主キーの値を検索します (性別 = 1 のすべての ID を検索します)。
  • 次に、見つかった主キー値に基づいて、主キー インデックスを通じて対応するデータ ブロックを検索します (ID に基づいて対応するデータ ブロックの内容を検索します)。
  • オフセットの値に従って、300001 番目の主キー インデックスのデータを照会し、前の 300000 件のレコードを破棄し、最後のレコードを取り出します。

しかし、セカンダリ インデックスですでにプライマリ キー値が検索されているのに、なぜプライマリ キー インデックスを使用して最初にデータ ブロックを検索し、次にオフセット値に基づいてオフセット処理を実行する必要があるのでしょうか。

主キーインデックスを見つけた後、まずオフセット処理を実行し、300,000 をスキップし、300,001 番目のレコードの主キーインデックスを通じてデータブロックを読み取ると、効率が向上します。

主キーのみをクエリした場合、何が違うのか確認する

mysql> メンバーから ID を選択し、性別を 1 に制限します (300000,1)。
+--------+
|id|
+--------+
|599465|
+--------+
セット内の1行(0.09秒)

明らかに、主キーのみをクエリすると、すべてのフィールドをクエリする場合に比べて実行効率が大幅に向上します。

投機

主キーのみをクエリする

セカンダリ インデックスが主キー値を見つけ、クエリは主キーのみを読み取る必要があるため、MySQL は最初にオフセット操作を実行し、その後、後続の主キー インデックスに基づいてデータ ブロックを読み取ります。

すべてのフィールドをクエリする必要がある場合

セカンダリインデックスは主キーの値のみを検索しますが、他のフィールドの値を取得するにはデータブロックを読み取る必要があるためです。したがって、MySQL は最初にデータ ブロックの内容を読み取り、次にオフセット操作を実行し、最後にスキップする必要がある前のデータを破棄して、後続のデータを返します。

確認

InnoDB には、データ ページやインデックス ページなど、最近アクセスされたデータ ページを格納するバッファー プールがあります。

テスト目的で、まず MySQL を再起動してから、バッファ プールの内容を確認します。

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('primary','gender') and TABLE_NAME like '%member%' group by index_name;
空セット (0.04 秒)

再起動後にデータ ページにアクセスされていないことがわかります。

すべてのフィールドをクエリし、バッファプールの内容を表示します。

mysql> メンバーから * を選択して、性別 = 1 の制限を 300000,1 にします。
+--------+------------+---------+
| ID | 名前 | 性別 |
+--------+------------+---------+
| 599465 | f48375bdb8 | 1 |
+--------+------------+---------+
セット内1列(0.38秒)

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('primary','gender') and TABLE_NAME like '%member%' group by index_name;
+------------+-----------+
| インデックス名 | カウント(*) |
+------------+-----------+
| 性別 | 261 |
| プライマリ | 1385 |
+------------+-----------+
セットに2行(0.06秒)

この時点で、バッファー プールにはメンバー テーブルのデータ ページが 1385 個、インデックス ページが 261 個あることがわかります。

MySQLを再起動してバッファプールをクリアし、主キーのみをクエリするテストを続行します。

mysql> メンバーから ID を選択し、性別を 1 に制限します (300000,1)。
+--------+
|id|
+--------+
|599465|
+--------+
セット内の1行(0.08秒)

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('primary','gender') and TABLE_NAME like '%member%' group by index_name;
+------------+-----------+
| インデックス名 | カウント(*) |
+------------+-----------+
| 性別 | 263 |
| プライマリー | 13 |
+------------+-----------+
セットに2行(0.04秒)

この時点で、バッファー プールにはメンバー テーブルのデータ ページが 13 個、インデックス ページが 263 個しかないことがわかります。したがって、主キー インデックスを介してデータ ブロックにアクセスする複数の I/O 操作が削減され、実行効率が向上します。

したがって、オフセットが大きすぎて MySQL クエリ時のパフォーマンスに影響を与える原因は、主キーインデックスを介してデータブロックに複数回アクセスする I/O 操作であることが確認できます。 (この問題が発生するのは InnoDB のみであり、MYISAM インデックス構造は InnoDB とは異なることに注意してください。セカンダリ インデックスはすべてデータ ブロックを直接ポイントするため、このような問題は発生しません)。

InnoDB と MyISAM エンジンのインデックス構造の比較

ここに写真の説明を記入してください

最適化手法

上記の分析に基づいて、すべてのフィールドをクエリすると、主キー インデックスによるデータ ブロックへの複数回のアクセスによって I/O 操作が発生することがわかります。

したがって、最初にオフセット主キーを見つけ、次に主キー インデックスに基づいてデータ ブロックのすべての内容を照会して最適化を実現します。

mysql> メンバーから a.* を内部結合として選択します (メンバーから id を選択し、性別 = 1、制限 300000,1)、b として a.id = b.id で選択します。
+--------+------------+---------+
| ID | 名前 | 性別 |
+--------+------------+---------+
| 599465 | f48375bdb8 | 1 |
+--------+------------+---------+
セット内の1行(0.08秒)

付録: MYSQL の制限、オフセットの違い

選択
  キーワード
から
  キーワードランク
どこ
  広告id='59'
並び替え
  キーワード
制限 2 オフセット 1;

たとえば、この SQL ステートメントでは、制限の後に 2 つのレコードが続き、オフセットの後に最初のレコードが続きます。

選択
  キーワード
から
  キーワードランク
どこ
  広告id='59'
注文する
  キーワード
制限 2,1;

この SQL では、制限に従って 2 番目の項目から読み取り、1 つの情報を読み取ります。

これら2つを混同しないでください。

要約する

上記はこの記事の全内容です。この記事の内容が皆さんの勉強や仕事に一定の参考学習価値を持つことを願っています。ご質問があれば、メッセージを残してコミュニケーションしてください。123WORDPRESS.COM を応援していただきありがとうございます。

以下もご興味があるかもしれません:
  • MySQL パフォーマンスの最適化: インデックスを効率的かつ正しく使用する方法
  • MySQL データベース クエリ パフォーマンス最適化戦略
  • MySQLの高性能最適化スキルの概要
  • MySQL パフォーマンス最適化のための魔法のツール、Explain の基本的な使用分析
  • MySQL スロークエリを通じて MySQL のパフォーマンスを最適化する方法
  • MySQL Limitパフォーマンス最適化とページングデータパフォーマンス最適化の詳細な説明
  • MySQL の集計関数 count の使用法とパフォーマンスの最適化テクニック
  • 数千万のデータを扱うMySQLのページングクエリのパフォーマンスを最適化する
  • MYSQL開発パフォーマンス調査:バッチデータ挿入の最適化方法
  • MySQL パフォーマンス最適化のヒント

<<:  JSはreduce()メソッドを使用してツリー構造データを処理します

>>:  Linux 環境の Apache サーバーでセカンダリドメイン名を設定する方法の詳細な説明

推薦する

Linux で大容量メモリ ページを持つ Oracle データベースを最適化する方法

序文PC サーバーは今日まで発展を続け、パフォーマンスにおいて大きな進歩を遂げてきました。 64ビッ...

Vue の新しい組み込みコンポーネントの使用方法の詳細な説明

目次1. テレポート1.1 テレポートの紹介1.2 テレポートの使用1.3 プレビュー効果2. サス...

サブクエリ最適化における MySQL 選択の実装

以下のデモはMySQLバージョン5.7.27に基づいています。 1. MySQLサブクエリ最適化戦略...

MySQLからClickHouseに移行する5つの方法

データ移行は、MySQL から ClickHouse にインポートする必要があります。概要プランは以...

vue3 のコンポーネントの互換性のない変更の詳細な説明

目次機能コンポーネント非同期コンポーネントの書き方とdefineAsyncComponentメソッド...

Ubuntu で時刻同期に NTP を使用する

NTP は、ネットワーク上で時間を同期するための TCP/IP プロトコルです。通常、クライアントは...

Tomcat は、Springboot プロジェクトの WAR パッケージの起動時にエラーを報告します: 子の起動時にエラーが発生しました

今日、会社の Springboot プロジェクトは、テストのためにテスト サーバーにデプロイする準備...

uni-appのスタイルの詳細な説明

目次uni-app のスタイル要約するuni-app のスタイルsassプラグインは公式ウェブサイト...

JavaScript に関する 6 つの奇妙で便利な点

目次1. 解体のヒント2. デジタルセパレーター3. try...catch...finally が...

新しい ECMAscript オブジェクト機能の紹介

目次1. オブジェクトのプロパティ1.1 属性表記2. プロパティ名を計算する3.オブジェクトメソッ...

DIVマスクを使用して、マウスでチェックボックスを直接チェックすることが無効である問題を解決します

フロントエンドの開発過程で、チェックボックスが必要な状況が発生しました。ユーザー操作の利便性を考慮し...

MySQL MyISAM と InnoDB の違い

違い: 1. InnoDB はトランザクションをサポートしていますが、MyISAM はサポートしてい...

HTML テーブルインライン形式の詳細な説明

インライン形式<colgroup>...</colgroup>属性名 属性値...

CentOS で LibreOffice を使用してドキュメント形式を変換する方法

プロジェクト要件では、アップロードされたドキュメントの前処理が必要です。ユーザーが doc 形式でド...

バージョン管理ツール Rational ClearCase の紹介

Rational ClearCase は、コードやその他のソフトウェア開発資産のバージョン管理を実...