この記事では、LinuxサーバーへのセルフホストでMySQLとPostgreSQLを選ぶ際に必ず理解しておくべき、ストレージエンジンの仕組みとロック機構の違いを解説します。InnoDBのクラスタードインデックスとPostgreSQLのヒープストレージがどう異なるか、MVCCの実装差がなぜ運用コストに直結するのかを、実際の調査コマンドと出力例とともに見ていきます。
実行環境: Rocky Linux 9.4 / MySQL 8.0.36・PostgreSQL 16.4 で動作確認済み。
この記事のポイント
・MySQLのInnoDBはPK順にデータを物理配置するクラスタードインデックス構造
・PostgreSQLはヒープ構造でdead tupleが蓄積するためVACUUMが運用上の必須コスト
・ロック競合の調査はMySQL: SHOW ENGINE INNODB STATUS / PostgreSQL: pg_locksで行う
・書き込み多め・PK検索中心→MySQL、複雑クエリ・整合性重視→PostgreSQLが実務の目安
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ「ストレージエンジン」と「ロック機構」で選ぶのか
MySQLとPostgreSQLの違いを「どちらが速いか」で語るのは、実はあまり意味がありません。どちらも適切に設定すれば高いパフォーマンスを出せますし、チューニングの余地も十分にあります。むしろLinuxサーバーでセルフホストする場合に採用判断の根拠にすべきは、次の2点です。
・ストレージエンジンの構造:データをどのようにディスクに書くか。これがインデックス設計・バックアップ速度・ディスク使用量の増減パターンに直結します。
・ロック機構の実装:同時接続が増えたときにどこで競合が起きるか。本番で突然デッドロックが発生したとき、調査コマンドすら違うため、後から習得するコストが高くなります。
この2点を理解しておけば、「なぜPostgreSQLはVACUUMが必要なのか」「MySQLでギャップロックが起きる条件は何か」といった疑問が自然に解けるようになります。
MySQLのInnoDBストレージエンジン:クラスタードインデックスとMVCC
MySQLのデフォルトストレージエンジンInnoDBの特徴は、クラスタードインデックス構造とundoログによるMVCCの2点に集約されます。1. クラスタードインデックスとは何か
InnoDBはテーブルのデータを主キー(PRIMARY KEY)の順番に従ってB+ツリーに格納します。テーブルのデータとインデックスが同一のB+ツリー構造に統合されています。これをクラスタードインデックスと呼びます。PK検索( のような等値検索)では、B+ツリーを1回降りるだけで該当行のデータが取得できます。一方、セカンダリインデックス(PK以外のカラムに張ったインデックス)を使う場合は、セカンダリインデックスを辿ってPKを取得し、さらにクラスタードインデックスを再検索する「二段階参照」が発生します。
インデックス情報は次のコマンドで確認できます。
# InnoDBテーブルのインデックス情報確認 mysql> SHOW INDEX FROM orders\G *************************** 1. row *************************** Table: orders Non_unique: 0 Key_name: PRIMARY Seq_in_index: 1 Column_name: order_id Collation: A Cardinality: 128450 Index_type: BTREE *************************** 2. row *************************** Table: orders Non_unique: 1 Key_name: idx_customer_id Seq_in_index: 1 Column_name: customer_id Collation: A Cardinality: 18320 Index_type: BTREE 2 rows in set (0.00 sec)
2. undoログによるMVCC:古いバージョンの管理
InnoDBはMVCC(Multi-Version Concurrency Control)をundoログで実現しています。行が更新されると、変更前の内容がundoログに書き出され、他のトランザクションはそこから「自分が見える版」を取得します。undoログはまたは独立したundoテーブルスペースに蓄積されます。長時間実行されるトランザクションが存在すると、undoログを古いバージョンまで保持し続けるため肥大化します。長時間トランザクションの確認は次のコマンドで行います。
# 長時間実行トランザクションの確認 mysql> SELECT trx_id, trx_started, trx_query, TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS elapsed_sec FROM information_schema.INNODB_TRX ORDER BY trx_started\G *************************** 1. row *************************** trx_id: 421832 trx_started: 2026-09-15 10:23:41 trx_query: NULL elapsed_sec: 3847 1 row in set (0.00 sec)
PostgreSQLのヒープストレージとMVCC:タプル多版管理
PostgreSQLのストレージ構造は、MySQLとは根本的に異なります。1. ヒープテーブルとdead tupleの蓄積
PostgreSQLのテーブルはデータを「ヒープファイル」として格納します。InnoDBのようにPK順に並べることはなく、ページ内の空き領域に順次追記されていきます。インデックスは別ファイルに格納され、インデックスエントリは常にヒープファイルの行(タプル)を指します。PostgreSQLのMVCCはタプルに可視性情報を直接埋め込む方式をとっています。行を更新すると、古い行をその場に残したまま新しい版のタプルをヒープに追記します。古い版のタプルを参照しているトランザクションが存在しなくなっても、そのタプルは自動的には削除されません。これが「dead tuple(不要タプル)」です。
dead tupleの蓄積状況は次のコマンドで確認できます。
-- テーブルごとのdead tuple数確認 SELECT schemaname, relname, n_live_tup, n_dead_tup, last_autovacuum, last_autoanalyze FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5; schemaname | relname | n_live_tup | n_dead_tup | last_autovacuum ------------+-----------+------------+------------+------------------------------- public | orders | 128450 | 31204 | 2026-09-15 14:30:22.891423+09 public | sessions | 42100 | 28300 | 2026-09-15 13:55:10.445123+09 public | products | 18200 | 3240 | 2026-09-15 14:10:05.112345+09 (3 rows)
2. VACUUMとautovacuumが必要な理由
dead tupleを回収してディスク領域を再利用可能にするのがコマンドの役割です。またVACUUMはXID(トランザクションID)の枯渇(XID周回)を防ぐためにも必須の処理です。通常の運用では、PostgreSQLが自動でVACUUMを実行する機能を有効化します。手動VACUUMの実行と出力は次のとおりです。
-- 手動VACUUMの詳細出力 testdb=# VACUUM VERBOSE orders; INFO: vacuuming "public.orders" INFO: scanned index "orders_pkey" to remove 31204 row versions DETAIL: CPU: user: 0.18 s, system: 0.04 s, elapsed: 0.62 s INFO: "orders": removed 31204 row versions in 412 pages DETAIL: CPU: user: 0.06 s, system: 0.02 s, elapsed: 0.18 s INFO: "orders": found 31204 removable, 128450 nonremovable row versions in 1830 out of 1830 pages DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 4217893 Skipped 0 pages due to buffer pins, 0 frozen pages. CPU: user: 0.44 s, system: 0.10 s, elapsed: 1.82 s VACUUM
ロック機構の比較:MySQLのギャップロックとPostgreSQLの行ロック
1. MySQLのギャップロックとデッドロックの仕組み
InnoDBのREPEATABLE READ(デフォルト分離レベル)では、範囲検索時に「ギャップロック(Gap Lock)」が発生します。ギャップロックとは、インデックスの値の「間」をロックする仕組みで、ファントムリード(幻の行追加)を防ぐために使われます。例えば の範囲検索でロックを取ると、その範囲内のインデックス値の「隙間」もロックされます。別のトランザクションがその隙間にINSERTしようとすると待機が発生し、互いに待ち合う状況になるとデッドロックが起きます。
デッドロックが発生すると、MySQLはにログを残します。
-- デッドロック情報の確認(最後に発生したデッドロック1件を表示) mysql> SHOW ENGINE INNODB STATUS\G ... ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2026-09-15 11:42:38 140382923776000 *** (1) TRANSACTION: TRANSACTION 421905, ACTIVE 0 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 42, query id 18234 192.168.10.x appuser INSERT INTO orders (customer_id, amount) VALUES (5012, 3500) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 312 page no 8 n bits 96 index PRIMARY of table . trx id 421905 lock_mode X locks gap before rec insert intention waiting ... *** WE ROLL BACK TRANSACTION (2)
2. PostgreSQLの行レベルロックと待機確認
PostgreSQLでは(デフォルト分離レベル)を使うことで、MySQLのような広範なギャップロックは発生しません。ただし、や外部キー制約による行ロックは発生します。ロック待機の状況はシステムビューとを組み合わせて確認します。
-- 現在のロック待機を確認するクエリ SELECT blocked.pid AS blocked_pid, blocked.query AS blocked_query, blocking.pid AS blocking_pid, blocking.query AS blocking_query, blocked.wait_event AS wait_event FROM pg_stat_activity AS blocked JOIN pg_stat_activity AS blocking ON blocking.pid = ANY(pg_blocking_pids(blocked.pid)) WHERE cardinality(pg_blocking_pids(blocked.pid)) > 0; blocked_pid | blocked_query | blocking_pid | blocking_query | wait_event -------------+------------------------------------------+--------------+--------------------------------+------------ 284 | UPDATE orders SET status=2 WHERE id=1042 | 271 | UPDATE orders SET amount=8800 | relation (1 row)
Linuxセルフホスト運用での採用判断基準
ここまでの内容を踏まえ、Linuxサーバーにセルフホストする際の選定基準をまとめます。MySQLとPostgreSQLはどちらも高品質なRDBMSですが、ワークロードの特性と運用チームの習熟コストで選択が変わります。セルフホストを検討している方向けに、Linux Master Pro SeminarではMySQLとPostgreSQLの実機構築・チューニングをハンズオンで実施しています。
・MySQLを選ぶべきケース:
- シンプルなCRUDが中心でPK等値検索が多い(EC系、ユーザー管理DBなど)
- スキーマ変更が頻繁でを多用する(InnoDBはOnline DDLが充実)
- PHPと組み合わせるLAMPスタックの既存資産がある
- VACUUMのような定期メンテナンス作業を最小化したい
・PostgreSQLを選ぶべきケース:
- ウィンドウ関数・CTE・JSONBなど複雑なSQLを多用する分析系ワークロード
- ACID準拠の厳密なトランザクション整合性が必要な金融・会計系システム
- PostGISやpgvectorなどの拡張機能を活用したい
- でギャップロックを回避し、高い同時並行性を実現したい
トラブルシュート:ロック競合調査の流れ
1. MySQL:デッドロックが頻発する場合
・まずでデッドロックログを確認する・ギャップロックが原因の場合、分離レベルをに変更することで解消できるケースがある
・インデックスが存在しないカラムで句を使うとテーブルロックになるため要注意
# ロック待ちタイムアウト秒数の確認 mysql> SHOW VARIABLES LIKE 'innodb_lock_wait_timeout'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_lock_wait_timeout | 50 | +--------------------------+-------+ 1 row in set (0.00 sec) # ロック待ちが多い場合は短縮して早期エラー検知(my.cnf で設定) # innodb_lock_wait_timeout = 10
2. PostgreSQL:クエリが長時間待機している場合
・でがになっているセッションを探す・でブロッキング元のPIDを特定する
・アイドル状態のまま長時間ロックを保持しているセッションはで終了させる
-- 60秒以上実行中のクエリを確認 SELECT pid, now() - query_start AS duration, query, state, wait_event_type FROM pg_stat_activity WHERE state != 'idle' AND (now() - query_start) > interval '60 seconds' ORDER BY duration DESC; pid | duration | query | state | wait_event_type -------+-----------+----------------------------------------------+--------+----------------- 1842 | 00:08:23 | UPDATE orders SET status=3 WHERE customer_id | active | Lock (1 row) -- ブロッキングセッションを強制終了(データは失われない) SELECT pg_terminate_backend(1842); pg_terminate_backend ---------------------- t (1 row)
本記事のまとめ
MySQLとPostgreSQLのストレージエンジン・ロック機構の違いを整理します。| 観点 | MySQL(InnoDB) | PostgreSQL |
|---|---|---|
| ストレージ構造 | クラスタードインデックス(PK順にデータ格納) | ヒープファイル(追記型・PKとデータ分離) |
| MVCC方式 | undoログで古いバージョンを管理 | ヒープにタプルを多版管理(xmin/xmax) |
| 不要データ回収 | 自動(purgeスレッドがundoログを削除) | VACUUM(autovacuum)が必要 |
| デフォルト分離レベル | REPEATABLE READ | READ COMMITTED |
| ギャップロック | あり(REPEATABLE READ時の範囲検索で発生) | なし(READ COMMITTED では発生しない) |
| ロック競合調査 | SHOW ENGINE INNODB STATUS |
pg_locks + pg_stat_activity |
| PK設計の重要度 | 高い(連番PKが物理I/Oに直結) | 中(ヒープのため物理順序は関係なし) |
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、MySQL・PostgreSQLの実機構築からロック競合の調査手順・チューニング設計まで、ハンズオンで体系的に学べるセミナーを提供しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:MySQL差分バックアップをxtrabackupで実装する手順|フル取得との組み合わせ設計
- この記事の属するカテゴリ:データーベース管理へ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます