クエリ多重実行で性能劣化が起きる原因と実践的な対策手順
同時アクセスが増える時間帯に限ってレスポンスが急激に悪化する――データベースを運用していると、この「クエリ多重実行による性能劣化」は避けて通れない課題です。単一クエリの最適化だけでは解決できず、CPU・メモリ・ディスクI/Oといったサーバーリソースの競合が根本原因になっているケースが大半を占めます。 本記事では、リソース競合がなぜ発生するのかを仕組みから掘り下げ、RDBMS別の診断SQLと段階的な改善手順を具体例つきで整理しています。 クエリ多重実行で性能劣化が発生するメカニズム データベースは同時に複数のクエリを処理する際、CPU時間・メモリバッファ・ディスクI/O帯域といった有限リソースを各セッションに分配します。同時実行数が一定の閾値を超えると、リソースの奪い合い(競合)が起き、個々のクエリの応答時間が指数関数的に悪化します。 リソース競合の3つのレイヤー レイヤー 競合対象 典型的な兆候 CPU 実行スレッド / ワーカープロセス CPU使用率100%張り付き、load average がコア数の数倍 メモリ 共有バッファ / ソートバッファ スワップ発生、OOM Killer起動、バッファキャッシュヒット率低下 ディスクI/O データファイル読み書き iowait の上昇、IOPS上限到達、WALの書き込み遅延 単一クエリが正常に動作する環境でも、同じクエリを10並列・50並列で実行すると急激に劣化する理由は、このリソース分配の上限にあります。たとえばPostgreSQLではshared_buffersのサイズが固定されているため、多数のクエリが同時にバッファプールを走査すると、キャッシュのスラッシング(頻繁な入れ替え)が起こり、本来キャッシュに乗るべきデータが追い出されてディスクI/Oが増大します。 ロック競合による連鎖的な待機 リソース競合と並んで問題になるのが、行ロックやテーブルロックによる待機の連鎖です。あるトランザクションがロックを保持したまま長時間実行されると、後続のトランザクションはロック待ちキューに入り、そのロック待ちがさらに次のトランザクションの待機を引き起こす「ロックチェーン」が形成されます。 -- PostgreSQL: 現在のロック待ち状況を確認する SELECT blocked.pid AS blocked_pid, blocked.query AS blocked_query, blocking.pid AS blocking_pid, blocking.query AS blocking_query, blocked.wait_event_type FROM pg_stat_activity AS blocked JOIN pg_locks AS bl ON bl.pid = blocked.pid AND NOT bl.granted JOIN pg_locks AS kl ON kl.locktype = bl.locktype AND kl.database IS NOT DISTINCT FROM bl.database AND kl.relation IS NOT DISTINCT FROM bl.relation AND kl.page IS NOT DISTINCT FROM bl.page AND kl.tuple IS NOT DISTINCT FROM bl.tuple AND kl.pid != bl.pid AND kl.granted JOIN pg_stat_activity AS blocking ON blocking.pid = kl.pid WHERE blocked.wait_event_type = 'Lock'; 性能劣化を引き起こす代表的な5つのパターン パターン1: 統計情報の陳腐化 データベースのクエリオプティマイザは、テーブルの行数やカラムの値の分布(統計情報)をもとに最適な実行計画を選択します。データの大量INSERT・DELETE後に統計情報が更新されないと、オプティマイザが非効率な実行計画を選び、フルテーブルスキャンやNested Loopが不要に発生します。 ...