MySQLクエリキャッシュに関するヒント

MySQLクエリキャッシュに関するヒント

序文

キャッシュの設計概念は、RDBMS データベースに広く浸透していることは周知の事実です。たとえば、コード行数が 2,500 万行にもなり、バグだらけと言われている Oracle データベースでは、SQL 実行プランをライブラリ キャッシュにキャッシュすることで、同じ SQL が再度実行されるときにハード パース (構文解析 -> 意味解析 -> 実行プランの生成) を回避できます。SQL 実行結果は RESULT CACHE メモリ コンポーネントにキャッシュされ、物理 IO を論理 IO に効果的に変換し、SQL 実行効率を向上させます。

MySQL の QueryCache は Oracle のものと似ています。SQL ステートメントのテキストとそれに対応する結果セットをキャッシュします。素晴らしいアイデアのように思えます。では、なぜ MySQL 5.6 ではデフォルトで無効にされ、5.7 では非推奨となり、MySQL 4.0 のリリース後のバージョン 8.0 では削除されたのでしょうか。今日は、MySQL QueryCache の過去と現在についてお話ししましょう。

QueryCache の概要

MySQL クエリ キャッシュ (QC: QueryCache) は、MySQL 4.0.1 で導入されました。クエリ キャッシュには、SELECT ステートメントのテキストとクライアントに送信された結果セットが格納されます。同じ SQL が再度実行されると、サーバーは SQL を再度解析して実行する代わりに、クエリ キャッシュから結果を取得してクライアントに返します。クエリ キャッシュはセッション間で共有されるため、あるクライアントによって生成されたキャッシュされた結果セットは、同じ SQL を実行する別のクライアントに応答できます。

冒頭の質問に戻りますが、SQL が共有されているかどうかをどのように判断するのでしょうか?

SQL テキストは、大文字、小文字、スペース、その他の文字が完全に同じであるかどうかで完全に一貫しているかどうかが判断されます。共有できます。共有の利点は、ハード解析を回避し、QC から直接結果を取得してクライアントに返すことができることです。次の 2 つの SQL は、一方が from で、もう一方が From であるため、共有されません。

--SQL 1
ID = 121 のアカウントから ID、残高を選択します。
--SQL 2
ID が 121 の場合、アカウントから ID、残高を選択します。

以下は、Oracle データベースが SQL_TEXT を通じて sql_id を生成するために使用するアルゴリズムです。sql_id が異なる場合は、同じ SQL ではなく共有されていないことを意味し、ハード解析が行われます。

#!/usr/bin/perl -w
Digest::MD5 qw(md5 md5_hex md5_base64) を使用します。
Math::BigInt を使用します。
my $stmt = "id = 121\0 のアカウントから id、balance を選択"; 
私の $hash = md5 $stmt; 
my($a,$b,$msb,$lsb) = unpack("V*",$hash);
私の$sqln = $msb*(2**32)+$lsb;
私の$stop = log($sqln) / log(32) + 1;
私の$sqlid = '';
私の$charbase32 = '0123456789abcdfghjkmnpqrstuvwxyz';
私の @chars = 分割 ''、$charbase32;
($i=0; $i < $stop-1; $i++){
 私の $x = Math::BigInt->new($sqln);
 私の$seq = $x->bdiv(32**$i)->bmod(32);
 $sqlid = $chars[$seq].$sqlid;
}
print "SQL は:\n $stmt \nSQL_ID は\n $sqlid\n";

SQL 1とSQL 2のコードによって生成されたsql_id値は異なっており、共有されていないことがわかります。

SQL は次のとおりです: select id, balance from account where id = 121 
SQL_IDはdm5c6ck1g7bdsです
SQL は次のとおりです: select id, balance From account where id = 121 
SQL_IDは6xb8gvs5cmc9bです

2 つの Java コード ファイルの内容を比較して違いを確認するように求められた場合、このコードを完全に理解するだけで、それを変換して独自のビジネス ロジックを実装できます。

クエリキャッシュ構成

mysql> '%query_cache%' のような変数を表示します。
+------------------------------+----------+
| 変数名 | 値 |
+------------------------------+----------+
| クエリキャッシュがある | はい |
| クエリキャッシュ制限 | 1048576 |
| クエリ キャッシュの最小解像度単位 | 4096 |
| クエリキャッシュサイズ | 16777216 |
| クエリキャッシュタイプ | オフ |
| query_cache_wlock_invalidate | オフ |
変数名説明
クエリキャッシュを持つクエリ キャッシュが使用可能かどうか。YES は使用可能、NO は使用不可です。標準バイナリ MySQL を使用する場合、値は常に YES になります。
クエリキャッシュ制限単一のクエリ結果セットの最大サイズを制御します。デフォルトは 1MB です。
クエリキャッシュ最小解像度単位クエリ キャッシュ シャード データ ブロックのデフォルト サイズは 4 KB で、ほとんどのビジネス シナリオに対応できます。
クエリキャッシュサイズクエリ キャッシュ サイズ (バイト単位)。0 に設定すると、QueryCache が無効になります。注: キャッシュ サイズを大きくしすぎないでください。スレッドは更新中に QueryCache をロックする必要があるため、非常に大きなキャッシュではロック競合の問題が発生する可能性があります。
クエリキャッシュタイプquery_cache_size>0 の場合、この変数は qc の動作に影響し、0、1、2 の 3 つの値を持ちます。0: キャッシュを無効にしたり、キャッシュされた結果を取得したりします。1: SELECT SQL_NO_CACHE を含むステートメントを除いてキャッシュを有効にします。2: SELECT SQL_CACHE で始まるステートメントのみをキャッシュします。

query_cache_min_res_unit の説明

デフォルトのサイズは 4KB です。結果セットが小さいクエリが多数ある場合、デフォルトのチャンク サイズではメモリの断片化が発生する可能性があります。断片化により、メモリ不足のためにクエリ キャッシュがキャッシュからクエリを削除するよう強制される場合があります。

この場合、query_cache_min_res_unit の値を減らすことができます。プルーニングにより削除された空きブロックとクエリの数は、Qcache_free_blocks および Qcache_lowmem_prunes ステータス変数の値によって示されます。多数のクエリで結果セットが大きい場合は、このパラメータの値を増やしてパフォーマンスを向上させることができます。

通常はQueryCacheモードを開きます

# MySQL 構成ファイル /etc/my.cnf を変更し、次の構成を追加して、MySQL サーバーを再起動します。
[mysqld]
クエリキャッシュサイズ = 32M
クエリキャッシュタイプ = 1

QueryCache の使用

まず、テスト データを取得して、QueryCache を無効にした場合と有効にした場合のシナリオをテストしてみましょう。

--ユーザー テーブル users を作成し、100 万件のデータを挿入します。
テーブル「users」を作成します(
 `id` bigint NOT NULL AUTO_INCREMENT、
 `name` varchar(20) NOT NULL DEFAULT '' COMMENT '名前',
 `age` tinyint NOT NULL デフォルト '0' コメント 'age',
 `gender` char(1) NOT NULL デフォルト 'M' コメント '性別',
 `phone` varchar(16) NOT NULL DEFAULT '' COMMENT '電話番号',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '作成時刻',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '変更時刻',
 主キー (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='ユーザー情報テーブル';

ユーザーからcount(*)を選択します。
+----------+
| カウント(*) |
+----------+
| 1000000 |

queryCache シナリオを無効にする

QueryCache を使用しない場合、同じクエリ ステートメントが実行されるたびにハード解析が発生し、大量のリソースが消費されます。

#QueryCache 設定を無効にする query_cache_size = 0
クエリキャッシュタイプ = 0

次のクエリを繰り返して実行時間を観察します。

-- クエリ ステートメントの最初の実行mysql> select * from users order by create_time desc limit 10;
+---------+------------+---------+-----------+-----------+----------------------+---------------------+
| ID | 名前 | 年齢 | 性別 | 電話番号 | 作成時間 | 更新時間 |
+---------+------------+---------+-----------+-----------+----------------------+---------------------+
| 997855 | ユーザー997854 | 54 | M | 15240540354 | 2020-12-15 14:34:50 | 2020-12-15 14:34:50 |
.......
10 行セット (0.89 秒)
-- 同じクエリを 2 回目に実行します。mysql> select * from users order by create_time desc limit 10;
+---------+------------+---------+-----------+-----------+----------------------+---------------------+
| ID | 名前 | 年齢 | 性別 | 電話番号 | 作成時間 | 更新時間 |
+---------+------------+---------+-----------+-----------+----------------------+---------------------+
| 997855 | ユーザー997854 | 54 | M | 15240540354 | 2020-12-15 14:34:50 | 2020-12-15 14:34:50 |
.......
10 行セット (0.90 秒)
-- プロファイル追跡 mysql> show profile cpu,block io for query 1; 
+----------------------+----------+-----------+-------------+--------------+---------------+
| ステータス | 期間 | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+-----------+-------------+--------------+---------------+
| 準備中 | 0.000022 | 0.000017 | 0.000004 | 0 | 0 |
| ソート結果 | 0.000014 | 0.000009 | 0.000005 | 0 | 0 |
| 実行中 | 0.000011 | 0.000007 | 0.000004 | 0 | 0 |
| データ送信中 | 0.000021 | 0.000016 | 0.000004 | 0 | 0 |
| ソートインデックスを作成しています | 0.906290 | 0.826584 | 0.000000 | 0 | 0 |

同じ SQL クエリ ステートメントを複数回実行した場合の実行時間は約 0.89 秒で、ほとんど差がないことがわかります。同時に、時間は主にソート インデックスの作成段階で消費されます。

queryCache シナリオを有効にする

クエリ キャッシュを有効にすると、クエリ ステートメントが初めて実行されるときに、SQL テキストとクエリ結果が QC にキャッシュされます。次に同じ SQL ステートメントが実行されると、データが QC から取得され、クライアントに返されます。

#QueryCache 設定を無効にする query_cache_size = 32M
クエリキャッシュタイプ = 1
-- クエリ ステートメントの最初の実行mysql> select * from users order by create_time desc limit 10;
+---------+------------+---------+-----------+-----------+----------------------+---------------------+
| ID | 名前 | 年齢 | 性別 | 電話番号 | 作成時間 | 更新時間 |
+---------+------------+---------+-----------+-----------+----------------------+---------------------+
| 997855 | ユーザー997854 | 54 | M | 15240540354 | 2020-12-15 14:34:50 | 2020-12-15 14:34:50 |
.......
10 行セット (0.89 秒)
-- 2 回目のクエリ ステートメントを実行します。mysql> select * from users order by create_time desc limit 10;
+---------+------------+---------+-----------+-----------+----------------------+---------------------+
| ID | 名前 | 年齢 | 性別 | 電話番号 | 作成時間 | 更新時間 |
+---------+------------+---------+-----------+-----------+----------------------+---------------------+
| 997855 | ユーザー997854 | 54 | M | 15240540354 | 2020-12-15 14:34:50 | 2020-12-15 14:34:50 |
.......
セット内の行数は 10 です (0.00 秒)
-- プロファイル追跡データmysql> クエリ 3 のプロファイル cpu、ブロック io を表示します。
+--------------------------------+----------+-----------+-------------+--------------+---------------+
| ステータス | 期間 | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+--------------------------------+----------+-----------+-------------+--------------+---------------+
| クエリ キャッシュ ロックを待機中 | 0.000016 | 0.000015 | 0.000001 | 0 | 0 |
| クエリのクエリ キャッシュを確認しています | 0.000007 | 0.000007 | 0.000000 | 0 | 0 |
| キャッシュされた権限をチェックしています | 0.000004 | 0.000003 | 0.000000 | 0 | 0 |
| 権限を確認しています | 0.000034 | 0.000033 | 0.000001 | 0 | 0 |
| キャッシュされた結果をクライアントに送信 | 0.000018 | 0.000017 | 0.000001 | 0 | 0 |

QueryCache が初めて実行されると、SQL テキストとデータはキャッシュされず、実行時間は 0.89 秒であることがわかります。QC が有効になっているため、SQL テキストと実行結果は QC にキャッシュされます。同じ SQL クエリ ステートメントが 2 回目に実行されると、QC が直接ヒットし、ハード解析なしでデータが返されるため、実行時間は 0 秒に短縮されます。プロファイルから、キャッシュされた結果をクライアントに送信すると、QC のデータが直接クライアントに返されることがわかります。

クエリキャッシュヒット率

クエリキャッシュ関連のステータス変数

mysql> 'QCache\_%' のようなグローバル ステータスを表示します。
+-------------------------+-----------+
| 変数名 | 値 |
+-------------------------+-----------+
| Qcache_free_blocks | 1 | --クエリ キャッシュ内の空きメモリ ブロックの数。
| Qcache_free_memory | 33268592 | --クエリ キャッシュの空きメモリの量。
| Qcache_hits | 121 | --QC から結果セットが取得された回数。
| Qcache_inserts | 91 | -- クエリ結果セットが QC に追加された回数。つまり、クエリは QC に存在しないことを意味します。
| Qcache_lowmem_prunes | 0 | -- メモリ不足のためクエリ キャッシュから削除されたクエリの数。
| Qcache_not_cached | 0 | --キャッシュされていないクエリの数。
| Qcache_queries_in_cache | 106 | --クエリ キャッシュに登録されているクエリの数。
| Qcache_total_blocks | 256 | -- クエリ キャッシュ内のブロックの合計数。

クエリキャッシュのヒット率と平均サイズ

           Qキャッシュヒット
クエリキャッシュヒット率 = ------------------------------------------------ x 100%
      Qcache_hits + Qcache_inserts + Qcache_not_cached
      
        query_cache_size = Qcache_free_memory
クエリ キャッシュ平均クエリ サイズ = --------------------------------------- 
          Qcache_queries_in_cache

更新操作がQCに与える影響

たとえば、支払いシステムの送金ロジックでは、まずアカウントをロックしてから残高を変更する必要があります。主な手順は次のとおりです。

クエリIDクエリ説明
1クエリキャッシュをリセットするクエリ キャッシュをクリアします。
2 ID = 121 のアカウントから残高を選択最初の実行で QC が失敗し、QC に追加されました。
3 ID = 121 のアカウントから残高を選択QC を押して結果を直接返します。
4アカウントを更新し、残高を balance - 1000 に設定し、ID を 121 に設定します。更新し、更新のためにクエリキャッシュをロックし、キャッシュされたデータを無効にします。
5 ID = 121 のアカウントから残高を選択キャッシュが無効化され、ミスとなり、QC に追加されました。
6 ID = 121 のアカウントから残高を選択QC を押して結果を直接返します。
この状況では、クエリ SQL が初めて実行されると、失敗してクライアントに結果が返されるため、QC は適していません。SQL テキストと結果セットを QC に追加した後、同じ SQL が次に実行されると、ハード解析操作なしで結果が QC から直接返されます。ただし、各更新では最初にデータを更新し、次に QC をロックしてからキャッシュされた結果を更新するため、以前にキャッシュされた結果は無効になります。同じクエリ SQL を再度実行すると、依然として失敗し、QC に再度追加する必要があります。QC のロック -> QC の確認 -> QC の追加 -> QC の更新という頻繁な処理により、多くのリソースが消費され、データベースの同時処理能力が低下します。

QueryCache を放棄する理由は何ですか?

一般的なビジネスシナリオ

業務システムの運用タイプから、OLTP(OnLine Transaction Processing)とOLAP(OnLine Analysis Processing)に分けられます。また、官公庁や企業向けでは、BOSS(Business Operation Support System)とBASS(Business Analysis Support System)に分けられます。この2種類のシステムの特徴をまとめてみましょう。

QueryCacheに適したシナリオ

まず、クエリ キャッシュ QC のサイズはわずか数 MB なので、キャッシュを大きくしすぎるのは適切ではありません。スレッドは更新プロセス中に QueryCache をロックする必要があるため、非常に大きなキャッシュではロック競合の問題が発生する可能性があります。では、クエリ キャッシュのメリットを享受できる状況とはどのようなものでしょうか?理想的な条件は次のとおりです。

  1. 同じクエリが、同じクライアントまたは複数のクライアントによって繰り返し発行されます。
  2. アクセスされる基礎データは、本質的に静的または半静的です。
  3. クエリはリソースを大量に消費したり、簡潔だが計算上複雑な結果セットを構築したりする可能性がありますが、結果セットは小さくなります。
  4. 同時実行性とクエリ QPS は高くありません。

これら 4 つの状況は、あくまでも理想的な状況です。実際の業務システムはすべて CRUD 操作があり、データの更新は比較的頻繁で、クエリ インターフェースの QPS は比較的高いです。したがって、上記の理想的な状況を満たすことができる業務シナリオはほとんどありません。考えられるのは、構成テーブルとデータ ディクショナリ テーブルは基本的に静的または半静的であり、QC を使用してクエリの効率を向上できるということです。

QueryCache に適さないシナリオ

テーブル データが頻繁に変更されると、クエリ キャッシュが無効になり、サーバー負荷が増加し、キャッシュからクエリが絶えず削除されるため、処理が遅くなります。データが数秒ごとに、またはより頻繁に更新される場合、クエリ キャッシュは適切ではない可能性があります。

同時に、クエリ キャッシュは単一のミューテックスを使用してキャッシュへのアクセスを制御します。これは、実際にはサーバーの SQL 処理エンジンにシングル スレッド ゲートウェイを課します。クエリ QPS が比較的高い場合、パフォーマンスのボトルネックになり、クエリ処理速度が大幅に低下する可能性があります。したがって、MySQL 5.6 ではクエリ キャッシュはデフォルトで無効になっています。

QueryCache の削除

クエリ キャッシュは MySQL 5.7.20 以降で非推奨となり、MySQL 8.0 では削除されます。非推奨には query_cache_type が含まれます。MySQL 5.6 のデフォルトの無効化から 5.7 の廃止、8.0 の完全削除まで、Oracle はさまざまな側面を総合的に検討した上でこの選択を行いました。

上記では、QueryCache に適したビジネス シナリオと適さないビジネス シナリオについて説明しました。この機能はビジネス シナリオに対する要求が厳しすぎて、実際のビジネスに合わせるのが難しいことがわかりました。さらに、有効にすると、データベースの同時実行性と処理能力が大幅に低下します。以下は、MySQL で QueryCache が無効 -> 非推奨 -> 削除された主な理由をまとめたものです。

同時に、クエリ キャッシュの断片化によってサーバーの負荷が増加し、データベースの安定性にも影響します。Oracle の公式サイトで QueryCache を検索すると、多くのバグがあることがわかります。そのため、MySQL 8.0 ではこの機能が直接かつ断固として削除されています。

要約する

上記では、MySQL QueryCache の起動 -> 無効化 -> 破棄 -> 削除のプロセスを紹介しました。これはもともと、SQL クエリの繰り返しによって発生するハード パーシング オーバーヘッドを削減し、物理 IO を論理 IO に変換して SQL 実行効率を向上させるために設計されました。しかし、MySQL は複数のバージョンの反復を経てきました。同時に、今日のハードウェア ストレージの急速な発展により、QC の利点はほとんどなくなり、データベースの同時処理能力も低下します。最終的に、バージョン 8.0 で直接削除されました。

実際、キャッシュ設計のアイデアは、ハードウェアとソフトウェアの分野に遍在しています。ハードウェアの面では、RAID カードと CPU には独自のキャッシュがあります。ソフトウェアの面では、OS キャッシュ、データベース バッファー プール、Java プログラム キャッシュなど、数え切れないほどあります。R&D エンジニアとして、ビジネス シナリオに応じて適切なキャッシュ ソリューションを選択することは非常に重要です。どれも適切でない場合は、独自のビジネス シナリオに合わせてキャッシュ開発をカスタマイズする必要があります。今日はこれですべてです。皆様のお役に立てば幸いです。

私はアオビンです。知れば知るほど、知らないことが増えます。いいね、お気に入り、コメントをくださった皆様、ありがとうございます。また次回お会いしましょう!

上記は、MySQL クエリ キャッシュに関するちょっとした知識の詳細な内容です。MySQL クエリ キャッシュの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • MySQLのよくある間違い
  • MySQL SHOW STATUSステートメントの使用
  • MySQLテクノロジーにおけるInnoDBロックの詳細な説明
  • MySQL マスタースレーブ同期、トランザクションロールバックの実装原理
  • MySQL MHA のセットアップと切り替えに関するいくつかのエラー ログの概要
  • mysql indexof関数の使用手順

<<:  マークアップ言語 - 簡略化されたタグ

>>:  jQuery はテーブルのページング効果を実装します

推薦する

JavaScript タイマーの詳細

目次1. 簡単な紹介2. 間隔を設定する2.1 説明2.2 パラメータ2.3 戻り値2.4 使用法3...

Ubuntu での Subversion (SVN) の詳細なインストールと設定

ソフトウェア開発者であれば、Subversion に精通している必要があります。 Subversio...

MySQL関数の簡単な紹介

目次1. 数学関数2. 文字列関数3. 日付関数4. 暗号化機能主な MySQL 関数は次のように紹...

DOCTYPE HTMLを使用する理由

これがないと、ブラウザはページをレンダリングするときに Quirks モードを使用することがわかって...

CSS3のtransform属性で実装される4つの機能

CSS3 では、transform 関数を使用して、テキストや画像の回転、拡大縮小、傾斜、移動という...

XHTML 入門チュートリアル: XHTML Web ページ画像アプリケーション

<br />適度に画像を追加すると、Web ページがより美しくなります。 画像タグ &l...

Vue: メモリリークの詳細な説明

メモリリークとは何ですか?メモリ リークとは、新しいメモリが作成されたが、解放またはガベージ コレ...

MySQLトリガーの概念、原理、使用法の詳細な説明

この記事では、例を使用して、MySQL トリガーの概念、原則、および使用方法を説明します。ご参考まで...

MySQLはライブラリ内の主キーなしでテーブルインスタンスコードを素早く取得します

概要MySQL データベースで主キーのないテーブルを表示するための SQL ステートメントをいくつか...

proxy_pass を設定した後に Nginx が 404 を返す問題を解決する

目次1. proxy_pass を設定した後に Nginx が 404 を返す問題のトラブルシューテ...

JavaScript プロトタイプのデータ共有とメソッド共有の実装を調べる

データ共有プロトタイプにはどのようなデータを書き込む必要がありますか?共有する必要があるデータはプロ...

Vueプロジェクトでページジャンプを実装する方法

目次1. vue-cli デフォルト プロジェクトを作成する (babel のみを含む) 2. 作成...

VMwareを使用したPermeateレンジシステムのインストール手順の詳細説明

1. 背景私たちは時々社内研修を行っており、実験環境をよく利用しています。最初はdockerコンテナ...

Vuex データの永続性を実装するためのアイデアとコード

vuexとはvuex: vue.js専用に開発された状態管理ツールで、すべてのコンポーネントの状態を...

HTML タグ マーキーはさまざまなスクロール効果を実現します (JS 制御なし)

ページの自動スクロール効果は JavaScript で実現できますが、今日偶然、JS 制御なしでさま...