innodb引擎支持多种事务隔离级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)、和串行化(serializable)。
表数据初始化
创建表test_tx_isolation,插入两条数据如下
create table test_tx_isolation(
id bigint(20) primary key auto_increment,
name varchar(255),
money bigint(20)
);
insert into
test_tx_isolation (name, money)
values ('bear', 2000),
('bob', 3000);
复制
读未提交:
读未提交隔离级别下,事务可以读到其他未提交的事务的修改。
session1隔离级别默认,将id为1的数据money修改为3000。
此时将sesion2隔离级别设置为read uncommitted,查询表发现id为1 的数据的money值已变成3000,但此时session1的修改并没有提交,若此时session1回滚,则id为1 的数据Money则恢复了2000,但此时session已经读到了3000这样一条中间态的数据,这种情况叫脏读。
读已提交:
读已提交隔离级别下,事务仅可以读到当前已提交的事务的修改。
session1隔离级别默认,将id为1的数据money修改为3000。
此时将Sesion2隔离级别设置为read committed,查询表发现id为1 的数据的money值是2000,没有读到脏数据,若此时session1提交,则id为1 的数据money被修改为3000,若此时session2再次读取该数据,则发现数据变为了3000, 大多数场景这样都是正常的,因为现在数据确实被修改了,但是在某些场景比如统计数据或者核对某段时间内的财务数据,同一个事务内两次统计结果不一样,比如第一次员工统计金额为5000,第二次上级统计为6000,这显然是不合理的,在同一个事务中,前后两次读取到的数据不一致情况叫不可重复读。
可重复读:
可重复读隔离级别下,同一个事务中多次读到的数据总是一致的。
将sesion2隔离级别设置为repeatable read,查询表发现有两条数据。
此时session1将表中id为1的数据的money改为2000并提交。
session2再次查询表数据,发现id为1 的数据money扔然是3000,和上一次查询的结果是一致的。
此时session1向表中插入一条id为3的数据并提交。
session2再次查询表数据,发现仍然是两条,此时session2试图向表中插入一条id为3的数据,但是却会报唯一键冲突,session2再次查询,但是表中数据仍然只有两条,session2明明查询没有id为3的数据,但是插入id为3的数据却会报出id唯一键冲突,对session2来说,就仿佛是幻觉一样,这就是幻读。
幻读怎么解决:
众所周知,幻读仅仅是针对插入这个操作,那么想要避免幻读,就要防止插入这个操作,假设Session2现在想要避免其它Session插入新的数据,那么session2就要锁住id为3后面的gap。
session2执行查询
select * from test_tx_isolation where id > 2 for update;
复制
session1尝试向表中插入新数据,就会被阻塞。
这是因为session2的查询显示加了排他锁,锁的区间是[3, +∞)(锁部分提过的next-key lock),因此session1想要插入一条数据就会被阻塞。
串行化:
串行化隔离级别下,无论是读还是写语句都会被加锁,查询加读锁,修改加写锁。session1查询id为1的数据。
session2也可以查询id为1的数据,但是如果想要修改,就会被阻塞。
多版本控制:
MySQL的可重复读是基于多版本控制实现的,假设id为1的数据此时money为2000版本为V1 ,session1 将其money修改为2200,此时版本为V2,这时如果session2读取id为1的数据,首先判断V2版本数据是否对其可见,如果可见,则返回V2,否则根据undo log计算出V1,继续判断是否可见,重复以上步骤,直到返回可见的第一个数据。值得注意的是,根据undo log计算可见数据版本仅是快照读, 当前读则是直接获取最新版本。快照读的可见性判断可以概括为,事务启动时,保存了当前活跃事务id(每个事务启动时会有一个递增的transaction id),和已提交事务的最小id,读取数据时,根据数据版本的事务id和自己的事务id以及保存的活跃事务id比对,如果数据版本在自己事务启动之前则可见,如果在事务自动之后则需要判断是否在启动时活跃事务中,若在则不可见,另外,如果某个版本是自己的事务所做的修改,也是可见的。
总结:
当前读, 读取数据最新版本,加锁的读写都是当前读
快照读:可重复读隔离级别下基于快照的查询,普通查询都是快照读
undolog: 和redo log bin log一样重要的日志,是MySQL实现多版本并发控制的必备日志,undo log可以认为是一个指向上一数据版本的回滚指针(实际上上一版本是计算得来)