说明
本文是《MySQL是怎样运行的-从根儿上理解MySQL》的学习笔记,文中的图全部来自于这本书,强烈建议买一本看看,对MySQL理解会特别深入,非常感谢作者"小孩子4919"。
第十八章 事务
事务的四大特性
- 原子性
事务中的操作要么全部执行成功,要么失败全部回滚. - 一致性
事务需满足一定的约束 - 隔离性
两个事务在执行过程中互不干扰 - 永久性
一旦事务执行成功,对数据库的影响是永久的
事务的状态转换
第二十一章 事务的隔离级别和MVCC
事务并发执行时遇到的一致性问题
-
脏读
一个事务读取到了另一个未提交事务修改的数据,意味着发生了脏读. -
不可重复读
如果一个事务修改了另一个未提交事务读取的数据,就意味着发生了不可重复读. -
幻读
如果一个事务先根据某些搜索条件查询出一些记录,在该事务未提交时,另一个事务写入了一些符合哪些搜索条件的记录,就意味着发生了幻读.
事务的隔离级别
mysql定义了4中隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED(读未提交) | 可能 | 可能 | 可能 |
READ COMMITTED(读已提交) | 不可能 | 可能 | 可能 |
REPEATABLE READ(可重复读) | 不可能 | 不可能 | 可能 |
SERIALIZABLE(串行化) | 不可能 | 不可能 | 不可能 |
MVCC原理
版本链
对于InnoDB存储引擎的表来说,它的聚簇索引记录中都包含以下两个必要的隐藏列
- trx_id
一个事务对某条记录进行修改时,都会把该事务的事务id赋值给trx_id - roll_pointer
每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中.这个隐藏列相当于一个指针,通过它可以找到该记录修改前的信息.
CREATE TABLE student(
sno int(10) NOT NULL AUTO_INCREMENT,
sname varchar(100),
age int(4),
PRIMARY KEY (`sno`)
));
INSERT INTO student VALUES(null, 's3', 12);
假设插入之后两个事务id分别为100和200的事务对这条记录进行UPDATE操作,操作流程如图21-3所示.
ReadView
对于InnoDB存储引擎来说,可重复读和读已提交都是通过ReadView实现的,下面就来介绍ReadView,它包含以下四个方面的内容
- m_ids
生成ReadView时活跃事务的id列表.活跃的事务是指未提交的事务 - min_trx_id
生成ReadView时活跃事务id的最小值,也就是m_ids的最小值 - max_trx_id
当前系统中应该分配给下一个事务的事务id值 - creator_trx_id
生成该ReadView的事务的事务id
事务隔离级别的核心问题是:版本链中那个版本对当前事务是可见的.有了ReadView就可以很方便判断,判断条件如下:
- 如果被访问记录的trx_id值与ReadView中creator_trx_id相同,说明是当前事务修改的记录,则该版本当前事务可见;
- 如果被访问记录的trx_id值小于min_trx_id,说明生成ReadView时该记录已经提交,则该版本当前事务可见;
- 如果被访问记录的trx_id值大于或等于max_trx_id值,说明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本对当前事务不可见;
- 如果被访问记录的trx_id值介于min_trx_id和max_trx_id之间,则判断trx_id是否在m_ids中,如果在,说明生成该版本事务是活跃事务,对当前事务不可见;如果不在,说明创建ReadView时生成该版本事务已经提价,该版本对当前事务可见.
总结
如果
t
r
x
_
i
d
>
=
m
a
x
_
t
r
x
_
i
d
trx\_id>=max\_trx\_id
trx_id>=max_trx_id或者
t
r
x
_
i
d
>
m
i
n
_
t
r
x
_
i
d
且
t
r
x
_
i
d
<
=
m
a
x
_
t
r
x
_
i
d
trx\_id>min\_trx\_id 且 trx\_id<=max\_trx\_id
trx_id>min_trx_id且trx_id<=max_trx_id,则该版本对当前事务不可见.如果某个版本对当前事务不可见,那么沿着版本链往下找到可见的版本,如果找到最后一条版本链还是不可见,那么该记录对当前事务不可见.
读已提交和可重复读区别在于,读已提交在事务每次SELECT语句时都会生成ReadView,而可重复读只在第一次SELECT时生成,此后不再发生变化.
第二十二章 锁
InnoDB中的锁
行锁
- 记录锁
最普通的锁,包括读锁S和写锁X - 间隙锁
sno | name |
---|---|
1 | s1 |
2 | s2 |
3 | s3 |
8 | s4 |
如果对sno=8的记录加上间隙锁,那么当前事务在未提交前,其他事务无法插入sno列值处于区间 ( 3 , 8 ) (3,8) (3,8)的记录,这就是间隙锁.mysql的间隙锁是为了解决部分幻读问题.
- 临键锁
有时候又想锁住某一条记录,又想阻止其他事务在该记录前面插入新记录,就可以对该记录加临键锁.也就是说,临键锁=记录写锁+间隙锁 - 插入意向锁
事务在等待加锁生成的锁称为插入意向锁
表锁
- 表读写锁
事务可以对整张表加读锁或者写锁.表级别的写锁与记录锁不兼容,表级别的读锁与记录读锁兼容 - 表级别意向锁
表级别的写锁与记录锁不兼容,那么在进行表级别加锁时怎么知道每一条记录是否加读写锁,InnoDB规定记录读锁加成功后也会加表级别的意向读锁IS,记录写锁加成功后也会加表级别的意向写锁IX.表级别的IS和表级别的读锁兼容,表级别的写锁和意向锁都不兼容