はじめに
TiDBはMVCCを採用しており、テーブルのレコードの変更は削除も含めてすべて保存されています。
実際にSELECTする際は、この履歴から最新のものを取得しています。
この動作は当然更新量が多くなればデータベースのパフォーマンスに影響を与えます。そのようなTiDBのMVCCのトラブルシュートについては下記の記事が詳しいです。
本記事では、TiDB Playground を使って、もっとプリミティブなレベルでTiDBのMVCCの挙動を理解していきます。
TiDB Playgroundの使い方については、私の過去記事を御覧ください。
超ざっくりとしたMVCCの解説
TiDBのMVCCは
- コミットした更新を
{トランザクション開始, トランザクション終了}のタイムスタンプをつけて記録している - Deleteしたレコードも削除マーカー(
tombstoneとよばれる)をつけて記録する -
GCと呼ばれる操作により、古い履歴は定期的に削除される
という動きをします。
この動きを見ていきましょう。そのための手順として、
- テーブルを作成して、レコードをいくつか挿入
- レコードを更新、削除する
- MVCC履歴の確認
- GCが発生したのち、MVCC履歴の確認(削除されているはず)
をやっていきます。
TiDBのGCはデフォルト10分間隔なので、このハンズオンには20分くらいかかります。お時間のあるときにやってみてください。
テーブルの作成と更新
まず TiDB Playgroundを起動し、mysqlクライアントで接続してください。
> tiup playground
Note: Version constraint is resolved to v8.5.4. If you'd like to use other versions:
<省略>
🎉 TiDB Playground Cluster is started, enjoy!
Connect TiDB: mysql --host 127.0.0.1 --port 4000 -u root
TiDB Dashboard: http://127.0.0.1:2379/dashboard
Grafana: http://127.0.0.1:3000
接続できたら、簡単なテーブルを作成して、更新します。
mysql> CREATE TABLE mvcc(id INT AUTO_INCREMENT PRIMARY KEY, create_at DATETIME DEFAULT CURRENT_TIMESTAMP);
Query OK, 0 rows affected (0.03 sec)
テーブルの作成
mysql> INSERT INTO mvcc VALUES (),(),();
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM mvcc;
+----+---------------------+
| id | create_at |
+----+---------------------+
| 1 | 2025-12-16 09:35:14 |
| 2 | 2025-12-16 09:35:14 |
| 3 | 2025-12-16 09:35:14 |
+----+---------------------+
テーブルを更新します。しばらく時間をおいてから(1分くらい)
-- 更新
mysql> UPDATE mvcc SET create_at=NOW() where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- 削除
mysql> DELETE FROM mvcc WHERE id=3;
Query OK, 1 row affected (0.00 sec)
--追加
mysql> INSERT INTO mvcc VALUES ();
Query OK, 1 row affected (0.00 sec)
-- 表示
mysql> SELECT * FROM mvcc;
+----+---------------------+
| id | create_at |
+----+---------------------+
| 1 | 2025-12-16 09:36:25 |
| 2 | 2025-12-16 09:35:14 |
| 4 | 2025-12-16 09:37:47 |
+----+---------------------+
MVCCレコードの確認
MVCCレコードの確認には、tidb-ctlコマンドを利用します。別のコンソールを開き、tiup コマンドのサブコマンドを利用します。
tidb-ctlのオプションは下記です。tiupのサブコマンドtidbとして実行します。
tiup ctl:<バージョン> tidb mvcc key -d <データベース名> -t <テーブル名> -i <PKの値>
> tiup ctl:v8.5.4 tidb mvcc key -d test -t mvcc -i 1
{
"key": "74800000000000007D5F728000000000000001",
"region_id": 122,
"value": {
"info": {
"writes": [
{
"start_ts": 462905772701122561,
"commit_ts": 462905772701122562,
"short_value": "gAABAAAAAggAAAAAGZlguBk="
},
{
"start_ts": 462905754141327362,
"commit_ts": 462905754141327364,
"short_value": "gAABAAAAAggAAAAAzphguBk="
}
]
}
}
}
id=1のMVCC履歴が表示されました。更新したので2つエントリがあります。ここで start_tsはトランザクションの開始タイムスタンプ(TSO)、commit_tsはコミット時のタイムスタンプです。short_valueはその時の値を示しています。
これらの情報はそれぞれエンコードされており、デコード可能なのですが本記事では扱いません。次回以降に確認していきます。
同様に、削除された id=3もみてみましょう。
> tiup ctl:v8.5.4 tidb mvcc key -d test -t mvcc -i 3
{
"key": "74800000000000007D5F728000000000000003",
"region_id": 122,
"value": {
"info": {
"writes": [
{
"type": 1,
"start_ts": 462905784248565762,
"commit_ts": 462905784248565763
},
{
"start_ts": 462905754141327362,
"commit_ts": 462905754141327364,
"short_value": "gAABAAAAAggAAAAAzphguBk="
}
]
}
}
}
先ほどと異なり、最初のMVCCエントリに "type":1という属性がありますね。これが削除マーカーです。削除されたことを示すエントリなので、short_valueはありません。
他のキーについても試してみてください。
ステイル読み取り(タイムトラベルクエリ)を試してみる
さて、履歴があることで、実は過去時点の断面を取り出すことが可能です。やってみましょう。
最初の断面を取り出すには、SELECT ... AS OF TIMESTAMP を利用します。
mysql> SELECT * FROM mvcc AS OF TIMESTAMP '2025-12-16 09:35:15';
+----+---------------------+
| id | create_at |
+----+---------------------+
| 1 | 2025-12-16 09:35:14 |
| 2 | 2025-12-16 09:35:14 |
| 3 | 2025-12-16 09:35:14 |
+----+---------------------+
更新されたレコードや、追加されたid=4のレコードがありませんね。create_atも揃っています。
途中断面ではどうでしょうか?
mysql> SELECT * FROM mvcc AS OF TIMESTAMP '2025-12-16 09:36:26';
+----+---------------------+
| id | create_at |
+----+---------------------+
| 1 | 2025-12-16 09:36:25 |
| 2 | 2025-12-16 09:35:14 |
| 3 | 2025-12-16 09:35:14 |
+----+---------------------+
最初の(id=1)更新のみが取得できています。id=3のDELETEや、id=4の追加はまだされていない状態です。
GCを実行する
TiDBではGCが定期的に実行されているのですが、最新のTiDBではGCが効率化されており、そのままでは古い履歴は削除されません。ここではあえて非効率なオプションを有効にして、実際に削除されるのを見てみましょう。
mysql> set config tikv gc.enable-compaction-filter = false;
Query OK, 0 rows affected (0.02 sec)
このオプションを設定しないと、TiDB PlaygroundではGCが発生してもMVCCエントリが削除されないので気をつけてください。
設定したオプションの解説はこのシリーズの別の記事で解説します。TiDB Cloudでは設定できません。
実際にGCが実行されたタイミングは、下記のSQLでわかります。
mysql> PAGER less -S
mysql> SELECT * FROM tidb;
+--------------------------+-------------------------------------------------------------------------------------------+------------------------------------->
| VARIABLE_NAME | VARIABLE_VALUE | COMMENT >
+--------------------------+-------------------------------------------------------------------------------------------+------------------------------------->
| bootstrapped | True | Bootstrap flag. Do not delete. >
| tidb_server_version | 220 | Bootstrap version. Do not delete. >
| system_tz | Asia/Tokyo | TiDB Global System Timezone. >
| new_collation_enabled | True | If the new collations are enabled. D>
| ddl_table_version | 4 | DDL Table Version. Do not delete. >
| tikv_gc_leader_uuid | 66c7e0171c40011 | Current GC worker leader UUID. (DO N>
| tikv_gc_leader_desc | host:jgray.local, pid:38806, start at 2025-12-15 09:54:24.667749 +0900 JST m=+3.677934709 | Host name and pid of current GC lead>
| tikv_gc_leader_lease | 20251216-09:51:37.617 +0900 | Current GC worker leader lease. (DO >
| tikv_gc_auto_concurrency | true | Let TiDB pick the concurrency automa>
| tikv_gc_enable | true | Current GC enable status >
| tikv_gc_run_interval | 10m0s | GC run interval, at least 10m, in Go>
| tikv_gc_life_time | 10m0s | All versions within life time will n>
| tikv_gc_last_run_time | 20251216-09:47:37.598 +0900 | The time when last GC starts. (DO NO>
| tikv_gc_safe_point | 20251216-09:37:37.598 +0900 | All versions after safe point can be>
| tidb_stats_gc_last_ts | 462905799500365824 | NULL >
| tikv_gc_mode | distributed | Mode of GC, "central" or "distribute>
+--------------------------+-------------------------------------------------------------------------------------------+------------------------------------->
ここで表示されている、tikv_gc_last_run_timeにGCが実行され、tikv_gc_safe_point以前の履歴レコードが削除されます。
GCは10分間隔で実行されるため、次の実行タイミングはtikv_gc_last_run_timeのおよそ10分後です。もしレコードの作成前にGCが実行されていた場合は、次のGCタイミングを待ってください。
MVCCレコードが削除されていることを確認
MVCCレコードが削除されていることは、ステイル読み取りを実行することでわかります。ステイル読み取りで指定する時間は、tikv_gc_safe_pointより後を指定する必要があります。
mysql> SELECT * FROM mvcc AS OF TIMESTAMP '2025-12-16 09:36:26';
ERROR 9006 (HY000): GC life time is shorter than transaction duration, transaction starts at 2025-12-16 09:36:26 +0900 JST, GC safe point is 2025-12-16 09:37:37.598 +0900 JST
また、tidb-ctl でも同様に確認できます。
❯ tiup ctl:v8.5.4 tidb mvcc key -d test -t mvcc -i 1
Starting component ctl: /Users/bohnen/.tiup/components/ctl/v8.5.4/ctl tidb mvcc key -d test -t mvcc -i 1
{
"key": "74800000000000007D5F728000000000000001",
"region_id": 122,
"value": {
"info": {
"writes": [
{
"start_ts": 462905772701122561,
"commit_ts": 462905772701122562,
"short_value": "gAABAAAAAggAAAAAGZlguBk="
}
]
}
}
}
# 削除された場合はinfoが空になる
❯ tiup ctl:v8.5.4 tidb mvcc key -d test -t mvcc -i 3
Starting component ctl: /Users/bohnen/.tiup/components/ctl/v8.5.4/ctl tidb mvcc key -d test -t mvcc -i 3
{
"key": "74800000000000007D5F728000000000000003",
"region_id": 122,
"value": {
"info": {}
}
}
まとめ
この記事では、TiDBの実際のMVCCレコードを確認することで、MVCCの履歴の持ち方やGCによる削除の様子を確認しました。MVCCの動きが具体的に理解できるのではないかと思います。
次回は、ここの動きをおさらいして、より詳細に動きを紹介していきたいと思います。