『High Performance MySQL 第4版』第3章まとめ:Performance Schema

技術系ノウハウ

『High Performance MySQL 第4版』を読んだ内容を章ごとにまとめています。


第3章 Performance Schema

概要

本章では、MySQLの内部動作を可視化するための仕組みである Performance Schema について解説する。
これはMySQLサーバ内部で発生しているさまざまなイベント(クエリ実行・ロック・I/O待機など)を低レベルで記録・分析できるメカニズムであり、パフォーマンスチューニングやボトルネック特定に不可欠な機能である。
一方で、誤ってすべての項目を有効化するとCPUやメモリに負荷がかかるため、仕組みを理解したうえで「必要な範囲のみを有効化」することが重要である。

Performance Schemaの基本概念

はじめに

データベースの性能を改善する際は、「どこで時間がかかっているのか」を把握することが第一歩となる。
Performance Schemaは、MySQL内部で発生するイベントを“計測用データベース”として蓄積し、それをSQLで参照できるようにしたもの。
質問に答えるためのデータベース(meta DB)であり、「なぜ遅いのか」を数値で説明するための仕組みといえる。

Instrument(インストゥルメント)

Instrument は「観測対象となるコード部分」を指す。
たとえばメタデータロックを観測したい場合は、wait/lock/metadata/sql/mdl というInstrumentを有効にする。
Instrumentの名称は階層構造を持ち、statement/sql/selectwait/synch/mutex/innodb/autoinc_mutex のように、「種別/サブシステム/詳細」の形式で構成される。

setup_instruments テーブルで有効・無効を確認でき、ENABLED列がYESなら監視対象となる。
なお、DOCUMENTATION列に説明が含まれる場合もあるが、NULLの場合も多い。

[MEMO]
Instrumentを増やすほどCPUコストが上がる。
特に「行ロック」など頻繁に発生する処理を計測するとCPU使用率が急上昇するため注意。

Consumer(コンシューマ)

Consumer は、Instrumentから送られてきた計測結果を受け取って保存するテーブル群を指す。
MySQL 8.0.25時点では110以上のテーブルが存在し、用途別に整理されている。

主なカテゴリ

分類説明
*_current / *_history / *_history_long現在・直近・過去のイベントを保存
events_waitsロックやmutexなどの待機イベント
events_statementsSQLステートメント単位の情報
events_stagesステージ(例: 一時テーブル作成)単位の情報
events_transactionsトランザクション単位の情報
summary tables集約情報(スレッド別・イベント別など)
instancesファイルやメモリなどのインスタンス情報
setup tables設定関連(有効/無効などの定義)

Digest(ダイジェスト)機能により、同じ構造のクエリをプレースホルダ化して集計できる。
例:

SELECT user, birthdate FROM users WHERE user_id=?;

この形式でまとめることで、SQLのバリエーションを1つのパターンとして集約できる。

Resource Consumption(リソース消費)

Performance Schemaのデータはすべてメモリ上に保持される。
容量を制御するには、コンシューマごとの最大サイズを設定する。
一部のテーブルは自動スケーリング(autoscaling)対応で、必要に応じてメモリを拡張するが、一度確保したメモリは解放されない。

また、計測対象(Instrument)が増えるほどCPU負荷も上がるため、目的に応じて最小限にすることが原則

Limitations(制限事項)

Performance Schemaにはいくつかの制約がある:

  • 対応していないコンポーネントは計測できない(例:一部のストレージエンジン)
  • 有効化した後からしかデータを収集できない
    → サーバ起動時に無効だった場合、過去の情報は得られない
  • 無効化しても確保済みのメモリは解放されない(再起動が必要)

sys Schema

MySQL 5.7以降では、Performance Schemaの補助として sysスキーマ が標準搭載された。
sysスキーマはPerformance Schemaの上に作られたビュー群であり、複雑な集計をわかりやすく表示する。
データ自体は保持せず、Performance Schemaの情報を読み出すだけ。

[MEMO]
sys スキーマのビューは、よく使う診断を簡単に実行できる。
例:sys.statements_with_full_table_scans でフルテーブルスキャンを検出。

Understanding Threads(スレッドの理解)

MySQLはマルチスレッド構造で動作する。
各スレッドには2種類のIDがあり、それぞれ次のように管理される。

項目意味
THREAD_IDPerformance Schema内で使われる内部スレッドID
PROCESSLIST_IDSHOW PROCESSLIST で表示される接続ID

⚠ THREAD_ID と PROCESSLIST_ID は別物。
ロックを保持している接続を強制終了する場合などは、threads テーブルを参照してPROCESSLIST_IDを特定する必要がある。

設定と構成

Performance Schemaの有効化

Performance Schema自体の有効・無効は起動時オプションで指定する:

performance_schema=ON

実行中に切り替えることはできない。

Instrumentの有効化・無効化

有効化には3通りの方法がある:

  1. setup_instruments テーブルを直接UPDATE
  2. sys.ps_setup_enable_instrument() ストアドプロシージャ
  3. 起動パラメータ performance-schema-instrument='instrument_name=ON'
UPDATE performance_schema.setup_instruments
SET ENABLED='YES' WHERE NAME LIKE 'statement/sql/%';

設定変更は再起動でリセットされる。永続化したい場合は設定ファイルで指定する。

Consumerの有効化

Consumerも同様に3通りの手段で制御できる:

  1. setup_consumers テーブルのUPDATE
  2. sys.ps_setup_enable_consumer() の呼び出し
  3. 起動パラメータ performance-schema-consumer='events_statements_history=ON'

代表的なConsumerの目的:

Consumer名内容
events_stages_*クエリの処理ステージ(例:一時テーブル作成)
events_statements_*SQL文単位の情報
events_transactions_*トランザクション単位
events_waits_*待機イベント(ロック・I/Oなど)
statements_digestクエリのダイジェスト統計
global_instrumentation / thread_instrumentation計測の全体有効化・スレッド単位制御

特定オブジェクトやスレッドの監視設定

setup_objects テーブルを使うと、スキーマ・テーブル・トリガー単位で監視のON/OFFを設定できる。

INSERT INTO performance_schema.setup_objects
(OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, ENABLED)
VALUES ('TRIGGER', 'test', '%', 'NO');

スレッドごとの制御は setup_threads テーブルで行い、
ユーザーごとのルールは setup_actors テーブルで管理する。

メモリサイズ調整

performance_schema の各テーブルはメモリ上に存在し、テーブルごとに最大件数やサイズを設定できる。

例)
performance_schema_events_stages_history_size=10
performance_schema_events_stages_history_long_size=10000

バッファサイズを調整することで、収集データ量とメモリ消費のバランスを取る。

デフォルト設定

  • MySQL 5.7以降はPerformance Schemaがデフォルト有効
  • MySQL 8.0ではさらに「メモリ」「メタデータロック」計測も有効化
  • mysql, information_schema, performance_schema の3スキーマは対象外

Performance Schemaの活用例

SQLステートメントの解析

events_statements_* テーブルを使うと、クエリ単位の詳細な実行情報を取得できる。

SELECT THREAD_ID, SQL_TEXT, ROWS_SENT, ROWS_EXAMINED, NO_INDEX_USED
FROM performance_schema.events_statements_history_long
WHERE NO_INDEX_USED > 0;

この結果から、インデックスを使わないクエリや一時テーブル生成の多いSQLを特定できる。
sys スキーマを使えばさらに簡単に確認可能:

SELECT query, total_latency, no_index_used_count
FROM sys.statements_with_full_table_scans;

主な指標(event_statement_history)

カラム意味重要度
CREATED_TMP_DISK_TABLESディスク上に一時テーブル作成(最適化要)
SELECT_FULL_JOINフルテーブルJOIN(インデックス不足)
SELECT_RANGE_CHECKインデックスなしJOIN
NO_INDEX_USED / NO_GOOD_INDEX_USEDインデックス未使用または非効率
SORT_SCANテーブルスキャンでソート
CREATED_TMP_TABLESメモリ内一時テーブル作成

[MEMO]
Performance Schemaを使うと、EXPLAINでは見えない実際の実行統計を収集できる。
アプリケーション側で遅延が発生しているとき、ここを照合すると原因追跡がしやすい。

まとめ

  • Performance SchemaはMySQL内部の動作を数値化するための仕組みで、問題の「原因」ではなく「結果」を明確にする。
  • Instrument(観測対象)とConsumer(結果保存先)の関係を理解するのが第一歩。
  • 必要な計測だけを有効化し、過剰な設定は避ける。
  • sysスキーマのビューを併用すると、一般的な分析を簡単に実行できる。
  • 計測データはリアルタイム解析やトラブル対応だけでなく、SLO(サービスレベル目標)改善の根拠にもなる。

業務での活かし方

  1. Performance InsightsやDatadogと併用する
    → クラウド監視では見えないロックやI/Oの詳細を把握できる
  2. スロークエリ調査の裏付けに使う
    → スロークエリログは「どのSQLが遅いか」まで、Performance Schemaは「なぜ遅いか」まで追える
  3. 監視コストを最小化する
    → 問題発生時だけInstrumentを限定的にONにする運用が現実的
  4. SRE的運用と連携
    → 「可用性」「レイテンシ」「エラー率」をSLOとして定義し、Performance Schemaのデータを数値根拠に使う

補足:Performance Schema 活用例(by ChatGPT)

具体例が知りたかったため、ChatGPTに出してもらいました。あくまでイメージになります。

events_statements_summary_by_digest

頻出クエリ・遅いクエリの把握

実務ユースケース
管理画面「顧客一覧」だけ遅い → rows_examined が異常に多い。

取得SQL

SELECT *
FROM performance_schema.events_statements_summary_by_digest
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 5;

サンプル結果

digest_textcount_staravg_timer_waitrows_examined_avg
SELECT * FROM customers ORDER BY created_at DESC15230.023 s120000
SELECT id FROM orders WHERE customer_id=?84200.008 s15

ポイント

120,000 行スキャン → インデックス不足
created_at にインデックス追加 or LIMIT 活用

events_waits_summary_by_instance

リソース遅延の原因を可視化

実務ユースケース
レポート生成が遅い → CPU ではなくロック待ちだった。

取得SQL

SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT/1000000000 AS wait_ms
FROM performance_schema.events_waits_summary_by_instance
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 5;

サンプル結果

EVENT_NAMECOUNT_STARwait_ms
wait/lock/row/exclusive201005234
wait/io/table/sql/handler5000842

ポイント
行ロック競合が多発
→ バッチ UPDATE と管理 SELECT が衝突

events_statements_history_long

過去の遅いクエリを”遡って”調査

実務ユースケース
深夜のレプリケーション遅延の原因解析。

取得SQL

SELECT EVENT_ID, SQL_TEXT, TIMER_WAIT/1000000000 AS time_ms
FROM performance_schema.events_statements_history_long
WHERE TIMER_WAIT > 1000000000
ORDER BY TIMER_WAIT DESC
LIMIT 10;

サンプル結果

EVENT_IDSQL_TEXTtime_ms
292919UPDATE orders SET status=’archived’ WHERE created_at < ‘2024-01-01’3421
292920SELECT * FROM orders WHERE created_at < ‘2024-01-01’ LIMIT 500001889

ポイント
大量 UPDATE が原因
→ チャンク分割で改善

memory_summary_by_thread_by_event_name

スレッドごとのメモリ使用量

実務ユースケース
CSV エクスポートがメモリ爆食 → OOM。

取得SQL

SELECT THREAD_ID, EVENT_NAME, SUM_NUMBER_OF_BYTES_USAGE AS bytes
FROM performance_schema.memory_summary_by_thread_by_event_name
ORDER BY bytes DESC
LIMIT 5;

サンプル結果

THREAD_IDEVENT_NAMEbytes
125memory/sql/SELECT820000000
126memory/sql/parse8200000

ポイント
SELECT で 800MB 使用
→ ストリーミング処理にリファクタリング

table_io_waits_summary_by_table

テーブルごとの I/O ボトルネック

実務ユースケース
顧客検索が遅い → 実は access_logs の INSERT が詰まっていた。

取得SQL

SELECT OBJECT_SCHEMA, OBJECT_NAME,
       COUNT_READ, COUNT_WRITE,
       SUM_TIMER_READ/1000000000 AS read_ms,
       SUM_TIMER_WRITE/1000000000 AS write_ms
FROM performance_schema.table_io_waits_summary_by_table
ORDER BY SUM_TIMER_WRITE DESC
LIMIT 5;

サンプル結果

OBJECT_NAMECOUNT_READCOUNT_WRITEwrite_ms
access_logs100990005320
customers12000200120

ポイント
ログ書き込みが本番検索を阻害
→ バルク化 / 非同期キュー化

file_summary_by_event_name

temp file や binlog の I/O を可視化

実務ユースケース
temp file が激増 → ORDER BY に必要なメモリ不足。

取得SQL

SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT/1000000000 AS time_ms
FROM performance_schema.file_summary_by_event_name
ORDER BY COUNT_STAR DESC
LIMIT 10;

サンプル結果

EVENT_NAMECOUNT_STARtime_ms
file/tmpfile120001800
file/innodb/innodb_log_file9500320

ポイント
一時ファイルが多い → ソート/集計の重さ
→ インデックス追加 or sort_buffer_size 調整

events_stages_history_long

クエリのどの段階が重いか

実務ユースケース
集計処理が遅い → GROUP BY がボトルネック。

取得SQL

SELECT NESTING_EVENT_ID, EVENT_NAME,
       TIMER_WAIT/1000000000 AS time_ms
FROM performance_schema.events_stages_history_long
ORDER BY TIMER_WAIT DESC
LIMIT 10;

サンプル結果

EVENT_NAMEtime_ms
stage/sorting1120
stage/creating tmp table880
stage/executing55

ポイント
sorting + tmp table が主犯
→ GROUP BY インデックス最適化へ

threads テーブル

現在の接続状態確認

実務ユースケース
バッチ暴走で接続枯渇 → 即特定。

取得SQL

SELECT THREAD_ID, PROCESSLIST_ID, PROCESSLIST_USER, PROCESSLIST_STATE
FROM performance_schema.threads
WHERE TYPE='FOREGROUND';

サンプル結果

THREAD_IDUSERSTATE
77batch_userSending data
78webappSleep
79webappLocked

ポイント
batch_user がロック保持
KILL 77 で解放


前回:第2章 監視と信頼性エンジニアリング
次回第4章 OSとハードウェアの最適化

コメント

タイトルとURLをコピーしました