MySQL のクエリパフォーマンスに対する制限の影響

MySQL のクエリパフォーマンスに対する制限の影響

I. はじめに

まず、MySQL のバージョンについて説明します。

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

テーブル構造:

mysql> desc テスト;
+--------+----------------------+------+-----+---------+----------------+
| フィールド | タイプ | Null | キー | デフォルト | 追加 |
+--------+----------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| val | int(10) 符号なし | NO | MUL | | |
| ソース | int(10) 符号なし | NO | | | |
+--------+----------------------+------+-----+---------+----------------+
セット内の 3 行 (.00 秒)

id は自動インクリメントの主キーであり、val は一意でないインデックスです。

合計500万件の大量のデータを投入します。

mysql> テストから count(*) を選択します。
+----------+
| カウント(*) |
+----------+
|5242882|
+----------+
セット1列(4.25秒)

制限オフセット行のオフセットが大きい場合、効率の問題が発生することが分かっています。

mysql> select * from test where val=4 limit 300000,5;
+---------+-----+--------+
| id | 値 | ソース |
+---------+-----+--------+
| 3327622 | 4 | 4 |
| 3327632 | 4 | 4 |
| 3327642 | 4 | 4 |
| 3327652 | 4 | 4 |
| 3327662 | 4 | 4 |
+---------+-----+--------+
5 列セット (15.98 秒)

同じ目的を達成するために、通常は次のように書き直します。

mysql> select * from test a 内部結合 (select id from test where val=4 limit 300000,5) b on a.id=b.id;
+---------+-----+--------+---------+
| id | val | ソース | id |
+---------+-----+--------+---------+
| 3327622 | 4 | 4 | 3327622 |
| 3327632 | 4 | 4 | 3327632 |
| 3327642 | 4 | 4 | 3327642 |
| 3327652 | 4 | 4 | 3327652 |
| 3327662 | 4 | 4 | 3327662 |
+---------+-----+--------+---------+
セット5行(0.38秒)

時間の違いは明らかです。

なぜ上記のような結果が表示されるのでしょうか? select * from test where val=4 limit 300000,5; のクエリ プロセスを見てみましょう。

インデックス リーフ ノード データが照会されます。

リーフ ノードの主キー値に基づいて、クラスター化インデックス上のすべての必須フィールド値をクエリします。

次の図のようになります。

上記のように、インデックス ノードを 300,005 回クエリし、クラスター化インデックス データを 300,005 回クエリし、最後に最初の 300,000 件の結果をフィルターして最後の 5 件を取り出す必要があります。 MySQL は、クラスター化インデックスのデータをクエリするために大量のランダム I/O を費やし、300,000 回のランダム I/O によってクエリされたデータは結果セットに表示されません。

誰かが必ずこう尋ねるでしょう: インデックスは最初に使用されるので、最初にインデックス リーフ ノードに沿って必要な最後の 5 つのノードまでクエリを実行し、次にクラスター化インデックス内の実際のデータをクエリするのはなぜですか。これには、次の図のプロセスと同様に、5 つのランダム I/O のみが必要です。

実は私もこの質問をしたいんです。

確認

上記の推論を確認するために実際にいくつかの操作を実行してみましょう。

select * from test where val=4 limit 300000,5を証明するには、MySQL に 1 つの SQL でインデックス ノードを介してデータ ノードがクエリされる回数をカウントする方法があるかどうかを知る必要があります。まずHandler_read_*シリーズを試してみましたが、残念ながらどの変数も条件を満たしませんでした。

私はこれを間接的にしか確認できません:

InnoDB にはバッファプールがあります。データ ページやインデックス ページなど、最近アクセスされたデータ ページが含まれます。したがって、バッファー プール内のデータ ページの数を比較するには、2 つの SQL ステートメントを実行する必要があります。予測結果では、 select * from test a inner join (select id from test where val=4 limit 300000,5)実行した後、バッファー プール内のデータ ページ数はselect * from test where val=4 limit 300000,5の対応する数よりもはるかに少なくなります。これは、前者の SQL はデータ ページに 5 回しかアクセスしないのに対し、後者の SQL はデータ ページに 300005 回アクセスするためです。

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

現在、バッファー プール内にテスト テーブルに関するデータ ページが存在しないことがわかります。

mysql> select * from test where val=4 limit 300000,5;
+---------+-----+--------+
| id | 値 | ソース |
+---------+-----+--------+
| 3327622 | 4 | 4 |
| 3327632 | 4 | 4 |
| 3327642 | 4 | 4 |
| 3327652 | 4 | 4 |
| 3327662 | 4 | 4 |
+---------+-----+--------+
セット5列(26.19秒)

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
+------------+-----------+
| インデックス名 | カウント(*) |
+------------+-----------+
| プライマリ | 4098 |
| 値 | 208 |
+------------+-----------+
セット2列(0.04秒)

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

select * from test a inner join (select id from test where val=4 limit 300000,5) 、バッファプールをクリアしてMySQLを再起動する必要があります。

mysqladmin シャットダウン
/usr/local/bin/mysqld_safe &
mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
空セット (0.03 秒)

SQL を実行します:

mysql> select * from test a 内部結合 (select id from test where val=4 limit 300000,5) b on a.id=b.id;
+---------+-----+--------+---------+
| id | val | ソース | id |
+---------+-----+--------+---------+
| 3327622 | 4 | 4 | 3327622 |
| 3327632 | 4 | 4 | 3327632 |
| 3327642 | 4 | 4 | 3327642 |
| 3327652 | 4 | 4 | 3327652 |
| 3327662 | 4 | 4 | 3327662 |
+---------+-----+--------+---------+
セットに5行(0.09秒)

mysql> select index_name,count(*) from information_schema.INNODB_BUFFER_PAGE where INDEX_NAME in('val','primary') and TABLE_NAME like '%test%' group by index_name;
+------------+-----------+
| インデックス名 | カウント(*) |
+------------+-----------+
| プライマリ | 5 |
| 値 | 390 |
+------------+-----------+
セットに2行(0.03秒)

2 つの違いは明らかです。最初の SQL は 4098 のデータ ページをバッファー プールにロードしますが、2 番目の SQL は 5 つのデータ ページのみをバッファー プールにロードします。私たちの予測通りです。これにより、最初の SQL ステートメントが遅い理由も確認できます。大量の役に立たないデータ行 (300,000) を読み取ってから破棄します。

そして、これは問題を引き起こします。あまりホットではないデータ ページを大量にバッファー プールにロードすると、バッファー プールの汚染が発生し、バッファー プールのスペースが占有されます。

発生した問題

再起動のたびにバッファー プールがクリアされるようにするには、innodb_buffer_pool_dump_at_shutdown と innodb_buffer_pool_load_at_startup をオフにする必要があります。これら 2 つのオプションは、データベースのシャットダウン時にバッファー プール データをダンプすることと、データベースの起動時にディスクにバックアップ バッファー プール データをロードすることを制御します。

参考文献:

1. https://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/

2. https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-buffer-pool-tables.html

SQL実行効率の詳細については、以下の関連記事をご覧ください。

以下もご興味があるかもしれません:
  • MySQL 選択最適化ソリューションに関する簡単な説明
  • MySQL で結果を選択して更新を実行する例のチュートリアル
  • MySQLの読み書き分離により挿入後にデータが選択されなくなる問題を解決
  • MySQL SELECT文の実行方法
  • MySQL で distinct メソッドを使用する詳細な例
  • MySQL で重複を削除するには、distinct または group by を使用する必要がありますか?
  • MySQL における distinct と group by の違い
  • MySQLのLIMIT文について詳しく説明します
  • union (all) と limit および exists キーワードの使用法を理解するための MySQL シリーズチュートリアル
  • MySQL での select、distinct、limit の使用

<<:  JS の FileReader を介して .txt ファイルの内容を取得する方法

>>:  HTML で dl(dt,dd)、ul(li)、ol(li) を使用する方法

推薦する

Vue エクスポート Excel 機能の全プロセス記録

目次1. フロントエンドのリーディングプロセス: 2. プラグインの使用と初期化2.1 vue-ad...

MySQL の基本ステートメントを最適化するための 10 の原則の概要

序文データベースの応用において、プログラマーは継続的な実践を通じて多くの経験を積んできました。これら...

Ubuntu の空き容量を増やす 5 つの簡単な方法

序文ほとんどの人は、システム ディスク ストレージが少ないときにこの操作を実行するか、Linux シ...

Datagrip2020 が MySQL ドライバーのダウンロードに失敗する

「downloadlaod」を直接クリックしてもダウンロードできない場合は、ここからダウンロードす...

Reactマウスの複数選択機能の設定方法

一般的に、リストには選択機能があり、単一選択、二重選択、複数選択が非常に一般的です。カスタム ループ...

Vue のローカルコンポーネントの紹介

Vueでは、ローカルコンポーネントを自分で定義(登録)することができます。コンポーネント名を定義する...

Vueはスクロールバースタイルを実装します

最初はブラウザのスクロールバーのスタイルを変更して効果を実現したいと思っていましたが、情報を調べてみ...

MySQL 5.6 zipパッケージのインストールチュートリアルの詳細

これまでは、拡張子が .msi のファイル、つまり、完全なインストールが使用されていました。しかし、...

MYSQLのバックアップデータのスケジュールクリアの特定の操作

1|0 背景プロジェクトの要件により、各月の履歴在庫データをアーカイブしてバックアップする必要があり...

Javascriptを使用して滑らかな曲線を生成する方法

目次序文ベジェ曲線の紹介二次ベジェ曲線3次ベジェ曲線ベジェ曲線計算機能フィッティングアルゴリズム付録...

Windows Server 2016 AD サーバーをセットアップする手順 (画像とテキスト)

導入: AD は Active Directory の略称で、中国語では Active Direct...

HTML 編集の基礎 (初心者必読)

DREAMWEAVER を開き、新しい HTML を作成します。 。ボディの特性: bgcolor...

15 分で学べる並列アーティファクト GNU Parallel 入門ガイド

GNU Parallel は、1 台以上のコンピューター上で計算タスクを並列に実行するためのシェル ...

MySQLのインデックス選択と最適化の詳細な説明

目次インデックスモデルB+ツリーインデックスの選択インデックスの最適化インデックスの選択性カバーイン...

ドメイン名、ポート、異なるIPに基づくnginx仮想ホスト設定の実装

1. nginx仮想ホストの設定仮想ホストを使用すると、実行する Web サイトごとに個別の Ngi...