上篇文章总结了有关事务的原子性,持久性和一致性【mysql】Innodb存储引擎是如何保证事务的ACID四个原则的,接下来是事务的隔离性。
事务隔离级别
- READ UNCOMMITTED : RU 称为浏览访问,可以读取事务未提交的数据。
- READ COMMITTED : RC 称为游标稳定,只能读取已经提交事务的数据。
- REPEATABLE READ: RR 可重复读。事务在执行过程中看到的值和事务开始时的值是一样的。
- SERIALIZABLE : SR 序列化。所有事务按照次序依次执行。当一个事务开始未提交时,其他事务均不能执行。
各个事务级别所造成的问题以及原因
-
RU:
问题:读取脏数据,不可重复读,幻像
原因:在RU隔离级别下,读未提交的数据,所以,当事务还没有提交时,数据并不是最终的数据,另一个事务就有可能读取到脏数据。 -
RC:
问题:不可重复读,幻读
原因:
不可重复读:在对数据进行修改时,会产生undo log,在undo log中,数据会有多个版本的快照数据这种技术成为行多版本技术(请阅读下面 undo log 存储版本链)。在RC级别下,innodb存储引擎每次读取的历史版本是最新的版本数据,因此,当有新的事务提交产生新的版本数据时,RC级别读取到的数据会发生不一致这种现象称为不可重复读(每次select会产生一个新的readView导致)。
幻读:在事务执行select操作加锁时,仍然可以进行insert操作对数据进行更改,再次执行select操作得到的结果集不一致的问题。这是因为在RC级别下不支持间隙锁,当select时加锁只能对记录本身的索引值加锁,不能对范围进行加锁,以致于可以继续插入范围内的数据造成幻读(下面解决方案中会举例说明)。 -
RR
问题:在RR级别下,innodb存储引擎解决了RC下的问题。可以达到隔离性的要求。
-
SR
此级别下由于是一个事务执行完成,另一个事务才能开始执行,事务级别最高,因此可以达到隔离性。
undo log 存储的版本链
版本链类其实是采用了乐观锁的机制。
在InnoDB引擎表中,它的聚簇索引记录中有两个必要的隐藏列:
- trx_id
这个id用来存储的每次对某条聚簇索引记录进行修改的时候的事务id。 - roll_pointer
每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入操作的undo日志没有这个属性,因为它没有老版本)
对于不同的操作,存储的数据如下:
SELECT
innodb会根据以下两个条件检查每行记录:
a.innodb只查找版本号早于当前事务版本的数据行,<=当前事务版本号,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的
b.行的删除版本要么未定义,要么大于当前的事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。
INSERT
INNODB为新插入的每一行保存当前系统版本号作为行版本号
DELETE
innodb为删除的每一行保存当前系统版本号作为行删除标识
UPDATE
innodb为插入一行新纪录,保存当前系统版本号为行版本号,同时保存当前系统版本号到原来的行作为行删除标识
- RR :在innodb存储引擎中,已基本解决以上的问题,可以达到和SR相同效果的事务隔离级别
- SR:有事务按照次序依次执行。当一个事务开始未提交时,其他事务均不能执行。不会出现以上问题。
问题描述
1、脏读:读到未提交事务数据时,称为脏读。即读到脏数据,并不是事务最终的数据。
2、不可重复读:是幻像问题中的一种。在事务进行中,重复读取统一条记录,显示的数据不同,称为不可重复读。
3、幻读:是幻像问题的一种。在事务进行中,重复读取记录,得到的记录数与之前不一致。如:在同一个事务中第一次读取有5条记录,第二次读取为6条记录,出现了幻读现象。
不可重复读和幻读都是幻像问题的一种表现形式。
解决方案
1、脏读:将数据库隔离级别修改为READ COMMITTED。只能读取提交事务的数据即可。在mysql数据库中读取的是事务版本号比自己小的数据。
2、不可重复读:在RC 级别由于上面所说的读取的历史数据版本总是为最新的版本,这个跟ReadView相关,每次select会生成一个新的ReadView,所以造成多次查询结果数据不一致。
解决方案:将隔离级别修改为REPEATABLE REA