TiDB 事务隔离级别

事务隔离级别是数据库事务处理的基础,ACID 中 I,即 Isolation,指的就是事务的隔离性。

sql 92标准定义了4种隔离级别,读未提交、读已提交、可重复读、串行化,见下表。

Isolation Level Dirty Read Nonrepeatable Read Phantom Read Serialization Anomaly
Read uncommitted Possible Possible Possible Possible
Read committed Not possible Possible Possible Possible
Repeatable read Not possible Not possible Not possible in TiDB Possible
Serializable Not possible Not possible Not possible Not possible

TiDB 实现了其中的两种:读已提交和可重复读。

TiDB 使用percolator事务模型,当事务启动时会获取全局读时间戳,事务提交时也会获取全局提交时间戳,并以此确定事务的执行顺序,如果想了解 TiDB 事务模型的实现可以详细阅读以下两篇文章:TiKV 的 MVCC(Multi-Version Concurrency Control)机制Percolator 和 TiDB 事务算法

可以通过以下命令设置 session 或者 global 的事务的隔离级别:

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL [read committed|repeatable read]

如果不使用 session 或者 global 关键字,这条语句只会对下一个执行的事务生效,不会对整个会话或者全局生效。

SET TRANSACTION ISOLATION LEVEL [read committed|repeatable read]

可重复读

可重复读是 TiDB 的默认隔离级别,当事务隔离级别为可重复读时,只能读到该事务启动时已经提交的其他事务修改的数据,未提交的数据或在事务启动后其他事务提交的数据是不可见的。对于本事务而言,事务语句可以看到之前的语句做出的修改。

对于运行于不同节点的事务而言,不同事务启动和提交的顺序取决于从 PD 获取时间戳的顺序。

处于可重复读隔离级别的事务不能并发的更新同一行,当时事务提交时发现该行在该事务启动后,已经被另一个已提交的事务更新过,那么该事务会回滚并启动自动重试。示例如下:

  1. create table t1(id int);
  2. insert into t1 values(0);
  3. start transaction; | start transaction;
  4. select * from t1; | select * from t1;
  5. update t1 set id=id+1; | update t1 set id=id+1;
  6. commit; |
  7. | commit; --回滚并自动重试

与 ANSI 可重复读隔离级别的区别

尽管名称是可重复读隔离级别,但是 TiDB 中可重复读隔离级别和 ANSI 可重复隔离级别是不同的,按照A Critique of ANSI SQL Isolation Levels论文中的标准,TiDB 实现的是论文中的 snapshot 隔离级别,该隔离级别不会出现幻读,但是会出现写偏斜,而 ANSI 可重复读隔离级别不会出现写偏斜,会出现幻读。

与MySQL可重复读隔离级别的区别

MySQL 可重复读隔离级别在更新时并不检验当前版本是否可见,也就是说,即使该行在事务启动后被更新过,同样可以继续更新。这种情况在 TiDB 会导致事务回滚并后台重试,重试最终可能会失败,导致事务最终失败,而 MySQL 是可以更新成功的。
MySQL 的可重复读隔离级别并非 snapshot 隔离级别,MySQL 可重复读隔离级别的一致性要弱于 snapshot 隔离级别,也弱于 TiDB 的可重复读隔离级别。

读已提交

读已提交隔离级别和可重复读隔离级别不同,它仅仅保证不能读到未提交事务的数据,需要注意的是,事务提交是一个动态的过程,因此读已提交隔离级别可能读到某个事务部分提交的数据。

不推荐在有严格一致要求的数据库中使用读已提交隔离级别。

事务重试

对于 insert/delete/update 操作,如果事务执行失败,并且系统判断该错误为可重试,会在系统内部自动重试事务。

通过配置参数 retry-limit 可控制自动重试的次数:

  1. [performance]
  2. ...
  3. # The maximum number of retries when commit a transaction.
  4. retry-limit = 10

语句回滚

在事务内部执行一个语句,遇到错误时,该语句不会生效。

  1. begin;
  2. insert into test values (1);
  3. insert into tset values (2); // tset 拼写错了,这条语句出错。
  4. insert into test values (3);
  5. commit;

上面的例子里面,第二个语句失败,其它插入 1 和 3 仍然能正常提交。

  1. begin;
  2. insert into test values (1);
  3. insert into tset values (2); // tset 拼写错了,这条语句出错。
  4. insert into test values (3);
  5. rollback;

这个例子中,第二个语句失败,最后由于调用了 rollback,事务不会将任何数据写入数据库。