記事の一覧

データベース監視とは?見るべき監視項目と確認SQL【PostgreSQL・MySQL】

データベース監視で見るべき項目を、死活・リソース・接続・クエリ性能・ロック・レプリケーション・容量・バックアップ・業務データの順に整理。PostgreSQL と MySQL でそのまま打てる確認 SQL と読み方つきで解説します。

「DB の監視をしておいて」と言われても、何を見ればよいのか、どこまで見れば足りるのかは分かりにくいものです。この記事ではデータベース監視で見るべき項目を 9 つに分け、それぞれ何が起きると困るのか、PostgreSQL と MySQL でどう確かめるのかを、実際に打てる SQL つきで説明します。DB の専門家でない開発者やインフラ担当の方が、自分の環境の監視を見直すときのチェックリストとして使えるようにしました。

データベース監視とは

データベース監視とは DB の状態を定期的に取得し、普段と違う状態になったら人に知らせる仕組みのことです。中身は「値を取る」「異常かどうかを判定する」「通知する」の 3 つに分けられます。

監視の目的は大きく 3 つあります。1 つめは止まったことにすぐ気づくことです。2 つめは止まる前の兆候 (ディスクが埋まりつつある、遅いクエリが増えている) に気づくことです。3 つめは障害が起きたときに原因を追えるよう、普段の値を記録として残しておくことです。「いつから遅くなったのか」という問いには、普段の値を残していなければ答えられません。

見るべき監視項目の全体像

まず全体を表にまとめます。上の 8 つは DB という仕組みの状態を見る項目で、最後の 1 つは DB に入っている業務の数字を見る項目です。

監視項目 放っておくと起きること 主に見る値
死活 DB に接続できず、サービスが止まる 接続できるか、応答時間
リソース 処理が詰まり、全体が遅くなる CPU、メモリ、ディスク I/O、キャッシュヒット率
接続 新しい接続が拒否される 接続数と上限、待機中・トランザクション中の接続
クエリ性能 画面やバッチが遅くなる 実行時間の合計・平均、実行回数
ロック 処理が待たされ、タイムアウトする ロック待ちの数と時間、デッドロック
レプリケーション 読み取り先に古いデータが出る、切り替えに失敗する 遅延、レプリケーションの停止
容量 書き込みができなくなる ディスクの空き、DB とテーブルのサイズの伸び
バックアップ 障害時に戻せない 最後に成功した時刻、WAL やバイナリログの保管
業務データ 技術的には正常なのに売上が止まる 注文数、決済の失敗率、ジョブの滞留

以下、項目ごとに確認の方法を見ていきます。SQL は PostgreSQL 18 と MySQL 8.4 の公式ドキュメントで確かめたものです。

1. 死活監視:DB に接続できるか

最初に用意するのは、DB が応答するかの確認です。PostgreSQL には pg_isready、MySQL には mysqladmin ping というコマンドがあり、どちらも接続を受け付けているかを返します。

pg_isready -h db.example.internal -p 5432
mysqladmin -h db.example.internal -u monitor -p ping

ただし、これだけでは「サーバーは動いているが、アプリのユーザーでは入れない」「接続はできるがクエリが返ってこない」状態を見逃します。アプリと同じネットワーク経路・同じ種類のユーザーで SELECT 1 を実行し、かかった時間も記録しておくと、より実態に近い死活監視になります。

2. リソース:CPU・メモリ・ディスク I/O

CPU 使用率、メモリ、ディスクの読み書きは、OS やクラウドの監視 (Amazon RDS なら CloudWatch) で取るのが一般的です。DB の中から見られる値としては、キャッシュヒット率が役に立ちます。データをメモリ上のキャッシュから読めた割合で、下がるとディスクからの読み込みが増え、遅くなります。

-- PostgreSQL: DB ごとのキャッシュヒット率 (起動または統計リセット以降の累積)
SELECT datname,
       round(blks_hit * 100.0 / nullif(blks_hit + blks_read, 0), 2) AS hit_ratio
FROM pg_stat_database
WHERE datname IS NOT NULL;
-- MySQL: InnoDB のバッファプールでディスクまで読みに行った回数と、読み取り要求の回数
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';

MySQL では Innodb_buffer_pool_reads ÷ Innodb_buffer_pool_read_requests がおおよそのキャッシュミス率です。

注意したいのは、どちらも起動してからの累積値だという点です。累積値の比率は長く動いている DB ほど動かなくなり、直近の悪化が埋もれます。5 分ごとなどに値を取り、前回との差分で率を計算してください。また、何 % なら正常という一律の基準はありません。データ量とメモリの比や、アクセスの偏りで普段の値は大きく変わるため、自分の DB の普段の値から下がったかどうかで見ます。

3. 接続:上限に近づいていないか

接続数が上限 (max_connections) に達すると、新しい接続はエラーになります。MySQL では「Too many connections」、PostgreSQL では「sorry, too many clients already」です。現在の数と上限を並べて見ます。

-- PostgreSQL: 状態ごとの接続数と上限
SELECT state, count(*)
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY state;

SHOW max_connections;
-- MySQL: 現在の接続数、起動以降の最大、上限に達して断った回数
SHOW GLOBAL STATUS
WHERE Variable_name IN ('Threads_connected', 'Threads_running',
                        'Max_used_connections', 'Connection_errors_max_connections');

SHOW VARIABLES LIKE 'max_connections';

数だけでなく中身も見てください。PostgreSQL で特に気をつけたいのは state が idle in transaction の接続です。トランザクションを開いたまま何もしていない状態で、ロックを持ち続けたり、不要な行の掃除 (VACUUM) を妨げたりします。長く続いているものは次の SQL で探せます。

SELECT pid, usename, application_name,
       now() - xact_start AS xact_age,
       left(query, 60) AS last_query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY xact_age DESC;

MySQL の Connection_errors_max_connections が増えていれば、上限に達して接続を断ったことが実際にあったという意味です。上限に達したときの対処は Too many connections の原因と対処 で詳しく説明しています。

4. クエリ性能:どのクエリに時間を使っているか

「DB が遅い」と言われたときに最初に知りたいのは、どのクエリが時間を使っているかです。1 回は速くても大量に呼ばれているクエリが、合計では一番重いことがよくあります。そのため、実行時間の合計で並べるのが基本です。

PostgreSQL では拡張機能 pg_stat_statements を使います。postgresql.conf の shared_preload_libraries に追加して再起動し、使う DB で CREATE EXTENSION pg_stat_statements; を実行します。

-- PostgreSQL 13 以降: 合計実行時間の上位 10 件 (時間はミリ秒)
SELECT calls,
       round(total_exec_time) AS total_ms,
       round(mean_exec_time::numeric, 2) AS mean_ms,
       rows,
       left(query, 80) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;

MySQL では Performance Schema の events_statements_summary_by_digest が、同じ形のクエリをまとめた統計を持っています。時間の列はピコ秒 (10 の 12 乗分の 1 秒) です。

-- MySQL: 合計実行時間の上位 10 件 (秒に換算)
SELECT SCHEMA_NAME,
       COUNT_STAR AS calls,
       ROUND(SUM_TIMER_WAIT / 1e12, 1) AS total_sec,
       ROUND(AVG_TIMER_WAIT / 1e12, 4) AS avg_sec,
       SUM_ROWS_EXAMINED,
       LEFT(DIGEST_TEXT, 80) AS query
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

これらも累積値です。監視では定期的に記録して「前の時間帯からの増え方」を比べると、リリース後に急に重くなったクエリが見つかります。個々の遅いクエリを調べる手順は MySQL のスロークエリ と PostgreSQL のスロークエリ の記事を参照してください。

5. ロック:待たされている処理はないか

ロック待ちは、別のトランザクションが行やテーブルを掴んでいるために、処理が先に進めない状態です。CPU もメモリも余裕があるのに遅い、というときによくある原因です。

-- PostgreSQL: ロック待ちのセッションと、待たせている相手の pid
SELECT pid,
       pg_blocking_pids(pid) AS blocked_by,
       now() - query_start AS waiting_for,
       left(query, 60) AS query
FROM pg_stat_activity
WHERE wait_event_type = 'Lock';
-- MySQL: InnoDB の行ロック待ちと、待たせている側のクエリ
SELECT wait_age, locked_table_name,
       waiting_pid, waiting_query,
       blocking_pid, blocking_query
FROM sys.innodb_lock_waits;

MySQL の blocking_query が NULL のときは、待たせている側のセッションがクエリを実行し終えて、トランザクションを開いたまま止まっています。アプリがコミットし忘れている可能性を疑います。なお pg_blocking_pids() は呼ぶたびにロック管理の共有領域を短時間占有するため、非常に短い間隔で呼び続けるのは避けてください。

デッドロック (2 つのトランザクションが互いの持つロックを待ち合い、DB がどちらかを強制終了する状態) は回数を記録します。PostgreSQL は pg_stat_database.deadlocks、MySQL は既定で有効なカウンター lock_deadlocks で数えられます。

-- PostgreSQL
SELECT datname, deadlocks FROM pg_stat_database WHERE datname IS NOT NULL;

-- MySQL
SELECT NAME, COUNT FROM information_schema.INNODB_METRICS WHERE NAME = 'lock_deadlocks';

詳しくは ロック待ち と デッドロック の記事で扱っています。

6. レプリケーション:遅れていないか、止まっていないか

レプリカ (複製先) を読み取りや待機系に使っている場合、遅延と停止の 2 つを見ます。遅延すると、書いたはずのデータがレプリカから読めない現象が起きます。停止に気づかないと、プライマリが落ちたときに古いデータで切り替えることになります。

-- PostgreSQL: プライマリ側で、レプリカごとの状態と遅延
SELECT application_name, client_addr, state,
       write_lag, flush_lag, replay_lag
FROM pg_stat_replication;

pg_stat_replication に行がなければ、レプリカが接続していません。遅延の列は、書き込みがなく完全に追いついている状態がしばらく続くと NULL になります。レプリカ側で now() - pg_last_xact_replay_timestamp() を見る方法もありますが、これは「最後に反映したトランザクションからの経過時間」なので、プライマリに書き込みがない時間帯は遅延がなくても増え続けます。夜間に誤報が出る原因になりやすい点に注意してください。

-- MySQL 8.0.22 以降: レプリカ側で実行 (8.4 では SHOW SLAVE STATUS は使えない)
SHOW REPLICA STATUS\G

MySQL では Replica_IO_Running と Replica_SQL_Running がどちらも Yes であること、Last_IO_Error と Last_SQL_Error が空であることを確かめます。遅延は Seconds_Behind_Source で見ますが、値が NULL のときはレプリケーションのスレッドが止まっていることを示します。NULL を「0 と同じ」と扱う監視設定にすると、停止を見逃します。原因の調べ方は レプリケーション遅延 の記事にまとめています。

7. 容量:ディスクが埋まる前に気づけるか

ディスクが埋まると、DB は書き込みができなくなり、場合によっては停止します。ディスクの空き容量そのものは OS やクラウドの監視で取り、DB 側では「何が増えているか」を見ます。

-- PostgreSQL: サイズの大きいテーブル (インデックスと TOAST を含む)
SELECT relname,
       pg_size_pretty(pg_total_relation_size(relid)) AS total_size
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(relid) DESC
LIMIT 10;
-- MySQL: スキーマごとのサイズ (MB)
SELECT table_schema,
       ROUND(SUM(data_length + index_length) / 1024 / 1024) AS size_mb
FROM information_schema.TABLES
GROUP BY table_schema
ORDER BY size_mb DESC;

MySQL 8.0 以降の information_schema.TABLES のサイズや行数は、information_schema_stats_expiry (既定 86400 秒) の間キャッシュされた値です。数時間単位の変化を追うなら、この点を踏まえて読んでください。

容量は「今何 % か」より「あと何日で埋まるか」で判断すると、対処に必要な時間を確保できます。1 日ごとのサイズを記録しておけば、増え方から残り日数を見積もれます。データ以外に、PostgreSQL の WAL や MySQL のバイナリログ、ログファイルが急に増えてディスクを埋めることもあります。

PostgreSQL では、もう 1 つ見ておきたい値があります。トランザクション ID の周回 (wraparound) です。VACUUM が十分に進まず古い行の凍結が遅れると、使えるトランザクション ID の残りが減っていきます。残りが少なくなると警告が出始め、さらに減ると、データを守るために新しいトランザクション ID の割り当てを拒否し、更新を伴う処理ができなくなります。

SELECT datname, age(datfrozenxid) AS xid_age
FROM pg_database
ORDER BY xid_age DESC;

xid_age が autovacuum_freeze_max_age (既定 2 億) を大きく超えて増え続けているなら、VACUUM が追いついていません。詳しくは PostgreSQL の VACUUM の記事を参照してください。

8. バックアップ:戻せる状態が続いているか

バックアップは「取る設定をした」だけでは足りず、「最後に成功したのがいつか」を監視します。失敗が何日も続いていたことに、復元が必要になってから気づく例は少なくありません。

PostgreSQL で WAL のアーカイブ (継続的なバックアップに使う、変更履歴ファイルの保管) を設定しているなら、pg_stat_archiver で成否を確認できます。

SELECT archived_count, last_archived_time,
       failed_count, last_failed_time
FROM pg_stat_archiver;

last_failed_time が last_archived_time より新しければ、直近のアーカイブが失敗しています。pg_dump や mysqldump、クラウドのスナップショットを使う場合は、ジョブの終了コードや作成されたファイルの時刻・サイズを確認し、決まった時刻までに新しいバックアップがなければ通知する形にします。定期的に別の環境へ実際に復元してみることも、監視の一部と考えてください。

9. 業務データ:注文や決済は普段どおりか

ここまでの 8 項目は、DB という仕組みが健康かどうかを見るものでした。しかし、CPU も接続数もレプリケーションも正常なのに、決済代行サービスの障害で決済がすべて失敗している、ということは起こります。アプリの不具合で注文が保存されていない、夜間バッチが止まって処理待ちのジョブが溜まっている、という場合も、技術的な指標には表れにくいものです。

こうした異常は業務データそのものを数えると見つかります。テーブル名や列名は例なので、自分のスキーマに合わせて読み替えてください。

-- 直近 1 時間の注文数と、1 週間前の同じ時間帯の注文数 (PostgreSQL)
SELECT
  count(*) FILTER (WHERE created_at >= now() - interval '1 hour') AS last_hour,
  count(*) FILTER (WHERE created_at >= now() - interval '7 days 1 hour'
                     AND created_at <  now() - interval '7 days') AS same_hour_last_week
FROM orders
WHERE created_at >= now() - interval '7 days 1 hour';
-- 直近 30 分の決済の失敗率 (PostgreSQL)
SELECT count(*) AS total,
       count(*) FILTER (WHERE status = 'failed') AS failed,
       round(count(*) FILTER (WHERE status = 'failed') * 100.0
             / nullif(count(*), 0), 1) AS failed_pct
FROM payments
WHERE created_at >= now() - interval '30 minutes';
-- 処理待ちのジョブの数と、いちばん古いものの待ち時間 (MySQL)
SELECT COUNT(*) AS pending,
       TIMESTAMPDIFF(MINUTE, MIN(created_at), NOW()) AS oldest_wait_min
FROM jobs
WHERE status = 'pending';

業務データの監視で難しいのは判定です。注文数は時間帯や曜日、キャンペーンで大きく変わるため、「1 時間に 10 件未満なら異常」のような固定の基準では、深夜に誤報が出るか、昼間の減少を見逃すかのどちらかになりがちです。上の例のように前週の同じ時間帯と比べる、件数が少ない時間帯は判定を緩める、といった工夫が要ります。基準の決め方は アラートの閾値の決め方 で詳しく説明しています。

また、監視のクエリが本番の負荷にならないよう、created_at のように絞り込みに使う列にインデックスがあるかを確認し、可能であればレプリカに対して実行してください。

監視を始めるときの進め方

9 項目すべてを一度に整えるのは大変です。おすすめは、死活、容量、バックアップ、接続、業務データの主要な数字 1〜2 個の順に整えることです。この 5 つは、気づかないとサービスの停止やデータの損失に直結し、しかも確認が比較的簡単です。そのあとで、クエリ性能・ロック・レプリケーションを足していきます。

監視用には専用の DB ユーザーを作り、権限は読み取りに絞ります。PostgreSQL なら事前定義ロール pg_monitor を付与すると、統計ビューや設定値を読めるようになります (GRANT pg_monitor TO monitor_user;)。MySQL では SHOW REPLICA STATUS に REPLICATION CLIENT 権限、他のセッションの情報を見るのに PROCESS 権限、Performance Schema と sys スキーマを読む権限が必要です。業務データを見るなら、対象テーブルの SELECT 権限も付けます。

最後に、通知を出す条件は「人が何か対応すべきとき」に絞ってください。記録はすべての項目について残し、通知は対応が必要なものだけにすると、通知が多すぎて大事なものが埋もれる事態を避けられます。この点は アラート疲れ の記事で扱っています。ツールの選び方は データベース監視ツールの選び方 を参照してください。

この確認を続けるには

ここで紹介した SQL は手で打っても、cron で定期的に実行して結果を記録しても使えます。Zabbix や Prometheus などの監視ツールに組み込めば、グラフとアラートにできます。ただ、業務データの監視はテーブルごとに何を数えるか、どこからを異常とするかを決める手間がかかり、後回しになりやすい部分です。

AI に任せる方法もあります。Scandock は、接続文字列を登録すると AI がテーブルの構成と実データを読み、注文数や決済の成否、ジョブの滞留のような業務の数字も含めて、何をどの間隔で監視するかを決めるサービスです。固定の閾値ではなく値の推移や過去の発火の結末を踏まえて異常かどうかを判断し、異常のときは読み取り専用のクエリで原因を調べ、原因・影響・根拠・対処案をつけて Slack やメールなどに通知します。

参考

関連する記事