首先,初始化一张表,通过例子来讲解今天的内容
CREATE TABLE hero (
number INT,
name VARCHAR(100),
country varchar(100),
PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;
事务是一组操作,要么全部执行,要么全部都不执行。
提起事务,首先想到的是事务的四个特性(ACID,详见//todo),关于事务的隔离性,主要讨论的还是多个事务并发处理同一个数据针对效率和事务隔离做的权衡。
对于先前已经读到的记录,之后又读取不到这种情况,其实这相当于对每一条记录都发生了不可重复读的现象。
幻读只是重点强调了读取到了之前读取没有获取到的记录。
按问题严重性排序
脏写 > 脏读 > 不可重复读 > 幻读
不论是哪种隔离级别,都不允许脏写的情况发生,因为脏写问题太严重了。
隔离级别下,如何避免脏写呢?使用排他锁
假定A先更新数据,会对更新的数据行记录加上排他锁(也叫写锁,悲观锁),除非事务A提交或终止从而释放排他锁,否则事务B都是无法更新数据的。
读未提交下事务B的更新操作也是需要等待事务A的排他锁释放.
InnoDB在实现MVCC时用到的一致性视图,用于支持RC和RR两种隔离级别的实现。
InnoDB里面每个事务都有一个唯一的事务ID,叫做transaction id,它是事务开始的时候向innoDB的事务系统申请的,顺序递增。
对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列:
所有的版本都会被roll_pointer属性连接成一个链表,这个链表叫做版本链,头节点代表的当前记录最新值。
假设两个事务id分别为100、200的事务对这条记录进行UPDATE操作,操作流程如下:
版本链如下
InnoDB提出ReadView,ReadView中主要包含4个比较重要的内容:
我们前边说过,只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都默认为0。
顺着版本链寻找,如果找不到的话,那就说明这条记录对当前事务不可见。
READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同。
使用READ COMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。
比方说现在系统里有两个事务id分别为100、200的事务在执行:
#Transaction 100
BEGIN;
UPDATE hero SET name = '关羽' WHERE number = 1;
UPDATE hero SET name = '张飞' WHERE number = 1;
# Transaction 200
BEGIN;
# 更新了一些别的表的记录
...
这个时候版本链如下
假设现在有一个使用READ COMMITTED隔离级别的事务开始执行:
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'
执行过程如下
之后,我们把事务id为100的事务提交一下,就像这样:
# Transaction 100
BEGIN;
UPDATE hero SET name = '关羽' WHERE number = 1;
UPDATE hero SET name = '张飞' WHERE number = 1;
COMMIT;
然后再到事务id为200的事务中更新一下表hero中number为1的记录:
# Transaction 200
BEGIN;
# 更新了一些别的表的记录
...
UPDATE hero SET name = '赵云' WHERE number = 1;
UPDATE hero SET name = '诸葛亮' WHERE number = 1;
版本链如下
然后再到刚才使用READ COMMITTED隔离级别的事务中继续查找这个number为1的记录,如下:
# 使用READ COMMITTED隔离级别的事务
BEGIN;
# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'刘备'
# SELECT2:Transaction 100提交,Transaction 200未提交
SELECT * FROM hero WHERE number = 1; # 得到的列name的值为'张飞'
这个SELECT2的执行过程如下:
REPEATABLE READ —— 在第一次读取数据时生成一个ReadView
按以上流程分析,Read View是不变的。
最终的核心问题是:形成的版本链是不变的,变得是不同时刻的readView,然后通过顺着版本链的头节点遍历,拿出节点中的事务ID,在当前事务的readView中判断当前版本对当前事务是否可见。
MVCC(多版本并发控制)指的就是在使用READ COMMITTD、REPEATABLE READ,在执行SELECT操作时访问记录的版本链的过程,这样子可以使不同的事务读-写、写-读操作并发执行,从而提升索引性能。
READ COMMITTD、REPEATABLE READ隔离级别最大的不同就是生成ReadView的时机不同,READ COMMITTD会每次执行普通SELECT 操作前都生成一个ReadView,而REPEATABLE READ只在第一次进行SELECT之后生成一个ReadView,之后的查询操作都重复使用这个ReadView。