2024.04.05

TiDBのREPEATABLE READの挙動

MySQL互換のNewSQLであるTiDBのREPEATABLE READの挙動を調査しました。

1.TiDB

TiDBはMySQL互換のNewSQLです。トランザクション含めてアプリケーションからはMySQLと同等に扱うことが可能な上に,Writeがスケールアウト可能という魅力的な製品です。
見かけ上MySQLに近いTiDBですが,内部のアーキテクチャはMySQLとは別物です。MySQLは,アーキテクチャの工夫によりREPEATABLE READを実現しています。
今回は,TiDBとMySQLでREPEATABLE READの挙動に差があるのか,調査してみます。

2.トランザクション分離レベルとREPEATABLE READ

トランザクションは複数同時に実行しても違いに影響を与えないことが理想です。しかし,現実に実装をする場合,技術的な制約(または性能を出すための意図)により,別のトランザクションに影響を及ぼすことがあります。
トランザクションがお互いに影響を与える度合いを,トランザクション分離レベルと呼びます。
※ トランザクション分離レベルはこちらの記事のように素晴らしい解説が多く存在しますので,説明は割愛します。
MySQLはデフォルトでREPEATABLE READというトランザクション分離レベルになっています。REPEATABLE READは,トランザクション間の影響が実用上問題ない程度に小さくなっています。詳細は上記の記事や,Web上の解説記事を参照してください。

3.MySQLとTiDBの動作の比較

それでは,早速MySQLとTiDBのREPEATABLE READの動作を比較します。

3.1 TiDBでファジーリードが発生しない

REPEATABLE READでは,ファジーリードは発生しません。もちろん,MySQLのREPEATABLE READでもファジーリードは発生しません。TiDBでも確認してみましょう。
-- TX A
mysql> begin;
Query OK, 0 rows affected (0.13 sec)
mysql> select * from test;
+----+------+
| id | c1   |
+----+------+
|  1 |   10 |
|  2 |   20 |
+----+------+
2 rows in set (0.13 sec)

-- TX B
mysql> begin;
Query OK, 0 rows affected (0.13 sec)
mysql> update test set c1 = 30 where id = 1;
Query OK, 1 row affected (0.11 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> commit;

-- TX A
mysql> select * from test;
+----+------+
| id | c1   |
+----+------+
|  1 |   10 |
|  2 |   20 |
+----+------+
2 rows in set (0.14 sec)  
TX Aは,TX Bがコミットする前にtestを参照しているため,TX Bがtestのデータを更新してコミットした後も変更前のデータが見えています。当然ですが,ファジーリードは発生していません。

3.2 TiDBでファントムリードが発生しない

MySQLのREPEATABLE READでは,ファントムリードが発生しません。TiDBではどうでしょうか。
-- TX A
mysql> begin;
Query OK, 0 rows affected (0.10 sec)
mysql> select * from test;
+----+------+
| id | c1   |
+----+------+
|  1 |   10 |
|  2 |   20 |
+----+------+
2 rows in set (0.12 sec)

-- TX B
mysql> begin;
Query OK, 0 rows affected (0.10 sec)
mysql> insert into test values(3, 30);
Query OK, 1 row affected (0.11 sec)
mysql> select * from test;
+----+------+
| id | c1   |
+----+------+
|  1 |   10 |
|  2 |   20 |
|  3 |   30 |
+----+------+
3 rows in set (0.11 sec)
mysql> commit;
Query OK, 0 rows affected (0.11 sec)

-- TX A
mysql> select * from test;
+----+------+
| id | c1   |
+----+------+
|  1 |   10 |
|  2 |   20 |
+----+------+
2 rows in set (0.11 sec)
TX BでINSERTしたデータは,TX Aから見えませんでした。つまり,TiDBのREPEATABLE READも,ファントムリードが発生しませんでした。このあたりの挙動はMySQLと同じなので,問題になることはなさそうです。

3.3 ギャップロック

-- TX A
mysql> begin;
Query OK, 0 rows affected (0.11 sec)
mysql> select * from test;
+----+------+
| id | c1   |
+----+------+
|  1 |   20 |
|  2 |   30 |
+----+------+
2 rows in set (0.10 sec)
mysql> select * from test where c1 = 30 for update;
+----+------+
| id | c1   |
+----+------+
|  2 |   30 |
+----+------+
1 row in set (0.11 sec)

-- MySQLなら,ここでc1の20〜30までがギャップロックされるはず

-- TX B
mysql> begin;
Query OK, 0 rows affected (0.10 sec)
mysql> insert into test values(3,25);
Query OK, 1 row affected (0.10 sec)

-- MySQLはギャップロックの解除待ちで,ここで待機が発生するはずだが,そのまま通った

mysql> commit;
Query OK, 0 rows affected (0.11 sec)
TiDBでは,MySQLと異なりINDEXのギャップに対するロックが発生しませんでした。非UNIQUEなINDEXに対するロックと,そのテーブルに対する挿入が同時に大量に走るワークロードでも,ロック待ちが発生せずに高いスループットが発揮されることが期待できます。

4.おまけ

TiDBにもInformationSchemaとPerformanceSchemaがあります。ただし,PerformanceSchemaは5.7相当なので,MySQL 8系のものに比べるとやや力不足です。とくに,近年は8系で強化されたPerformanceSchemaを使って監視・運用を行っている方が多いと思いますので,そのあたりは少々不便かもしれません。
図1:TiDBのinformaction_schema

図2:TiDBのperformance_schema

5.まとめ

TiDBでトランザクション分離レベルREPEATABLE READの検証を行いました。REPEATABLE READの要件を満たすだけでなく,MySQLと同様にファントムリードも発生しませんでした。また,INDEXのギャップに対するロックはMySQLよりも範囲が狭く,より高いスループットが期待できることが分かりました。

次世代システム研究室では,グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。アプリケーション開発者の方,次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら,ぜひ募集職種一覧からご応募をお願いします。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事