流水沉微

MySQL

事务

隔离级别[1]

隔离级别 脏读可能性 不可重复读可能性 幻读可能性 加锁读
读未提交 Read uncommitted
读已提交 Read committed ×
可重复读 Repeatable read × × ×
串行化 Serializable × × ×

读未提交 Read uncommitted

事务中的修改即使没有提交,对其他事务也是可见的

事务可以读取到未提交的数据,被称为脏读

性能并不比其他级别好很多,但是问题多多,一般不用

读已提交 Read committed

事务开始时,只能看见已经提交了的事务所做的修改

事务从开始到直到提交之前,其所做的修改对其他事务来说是不可见的

问题:可能会产生不可重复读的问题,因为两次执行同样的查询,可能得到不同的结果

场景:事务A执行过程中先后查询了两次,事务B在事务A的两次查询之间修改了符合事务A的查询条件的记录,导致事务A中的两次查询结果不同

可重复读 Repeatable read(MySQL的默认事务隔离级别)

可重复读解读了脏读和不可重复读的问题,保证了同一个事务中多次读取同样记录的结果一致,但是不能解决幻读(Phantom Read)的问题

幻读:当事务A在读取某个范围内的记录时,另一个事务又在这个范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行

场景:事务A先查询了数据库中最大的ID为9,于是给自己将要插入的数据的ID设为10,在事务A进行过程中,事务B先插入了一个ID为10的记录,事务A在插入过程中报错,因为INSERT也是一次隐式的读取

InnoDB和XtraDB通过MVCC解决了读数据情况下的幻读问题,对于修改和新增依然存在幻读,于是又发展了next-key locking来处理修改和新增场景下的幻读,当前的MySQL应该是没有幻读问题的

串行化 Serializable

Serializable
最高隔离级别,所有事务串行执行
Serializable
会在所读取的每一行数据上都加锁

可能导致大量的超时和锁争用的情况,需要保证强一致性和可以接受没有并发的情况下使用

MVCC

MVCC只在Repeatable read和Read committed两个隔离级别下工作

Read uncommitted永远只读最新版本,而不是符合当前事务版本的数据行,Serializable会对所有的读数据加锁

版本号[2]

  1. 系统版本号:一个递增的数字,每开始一个新的事务,系统版本号就会自动递增
  2. 事务版本号:事务开始时的系统版本号

SELECT

select时读取数据的规则为:

  1. 创建版本号<=当前事务版本号
  2. 删除版本号为空或>当前事务版本号

创建版本号<=当前事务版本号保证取出的数据不会有后启动的事务中创建的数据

删除版本号为空或>当前事务版本号保证了至少在该事务开启之前数据没有被删除,是应该被查出来的数据。

INSERT

为新插入的每一行保存当前系统版本号作为行版本号

UPDATE

插入一条新纪录,保存当前事务版本号为行创建版本号

同时保存当前系统版本号到原来的行作为行删除版本号

DELETE

为删除的每一行保存当前系统版本号作为行删除版本号

参考文献

[1] 高性能MySQL 第三版 P8 事务隔离级别 /P12 MVCC 豆瓣读书

[2] MySQL的可重复读级别能解决幻读吗 掘金

1 Jan 2017