一 事务的相关概念
1.1 事务的属性
原子性
一组操作要么全部成功,要么全部失败
隔离性
不同事务操作的影响范围
持久性
事务提交后,对数据库的影响已经永久不会改变
一致性
一致性可以是两个事务之间前后的操作对数据库的整体影响守恒
原子性,隔离性质和持久性一起保证一致性
1.2 事务的隔离级别
读未提交
事务A读取到了事务B还没有提交的数据,存在读未提交,不可重复读,幻读的问题
读已提交
事务A读取到事务B已经提交的数据,存在不可重复读,幻读的问题.
可重复读
事情A在一个事务中读取的数据都是一致的,存在幻读的问题
串行化
事务A和事务B都必须是顺序执行的,不能并发执行,解决的幻读的问题
二 MySQL中如何保证事务的隔离级别
在MySQL中的读取分为两种,一种是快照读,这种读取的事务的隔离通过MVCC来保证.
另一种是当前读,通过锁机制来保证事务的隔离级别
2.1 快照的隔离级别保证
2.1.1 undolog
在mysql提交或者更新一个记录的时候,会生成这个版本的undolog记录在这个行之后, 如下图:
2.2.2 MVCC
MVCC是一种视图,包含了启动当前事务时候的最小的事务ID,最大的事务ID,以及活跃的事务ID.
读取一行数据的时候,如果该行第一个值的事务ID小于MVCC的最小值,那么该值对本事务可见.
如果第一个值的事务大于MVCC的最大值,说明这一个值对本事务不可见,需要顺着指针找下一个undolog版本中的数据
如果一个值的事务ID在MVCC中的最大值和最小值之间,就需要判断下它是不是活跃的事务ID,如果不是的话,该数据就对本事务可见,否则不可见需要顺者undolog中寻找之前的版本
2.2.3 隔离级别的实现
可重复读的隔离级别下是在事务启动的时候都生成一个MVCC视图
读已提交是在每一个SQL执行的时候生成一个MVCC视图
2.2 当前读的隔离级别的实现
当前读事务的隔离级别是靠锁来实现的
2.2.1 锁的概念
行锁
数据库表中的每一个行加上锁
间隙锁
数据库中行和行之间间隙加上锁
next-key Lock锁
行锁+行锁左边的间隙锁
插入意向锁
当需要对一个表进行表级别的操作时候,必须保证没有别的事务进行表级别的操作或者没有行锁在表内,为了不对每一行进行遍历,mysql提出了插入意向锁
当获取一个行锁前,必须获取到这个表的插入意向锁
2.2.2 具体语句分析准备
事务的隔离级别,使用的索引和查询条件,以及具体执行语句的类型都会导致不同的加锁行为
- 首先建立一个表
CREATE TABLE hero (
number INT,
name VARCHAR(100),
country varchar(100),
PRIMARY KEY (number),
KEY idx_name (name)
) Engine=InnoDB CHARSET=utf8;
numbers是主建,name是二级索引
- 插入几条数据
INSERT INTO hero VALUES
(1, 'l刘备', '蜀'),
(3, 'z诸葛亮', '蜀'),
(8, 'c曹操', '魏'),
(15, 'x荀彧', '魏'),
(20, 's孙权', '吴');
2.2.3 查询语句加锁分析
锁定读的四种语句
1.SELECT ... LOCK IN SHARE MODE;
2.SELECT ... FOR UPDATE;
3.UPDATE ...
4.DELETE ...
(1) 可重复读隔离级别
#使用主键进行等值查询
SELECT ... LOCK IN SHARE MODE模式
SELECT * FROM hero WHERE number = 8 LOCK IN SHARE MODE;
主键具有唯一性质,如果在一个事务中第一次执行上述语句得到一个记录,因为主键具有唯一性,所以读取两次不会存在幻读现象,只需要在number值为8的记录加上一个行级别的锁(行锁中的共享锁)
SELECT * FROM hero WHERE number = 7 LOCK IN SHARE MODE;
因为number=7的行记录并不存在,所以我们需要加上一个gap锁防止别的事务插入这个数据,造成幻读
SELECT ... FROM UPDATE模式
SELECT * FROM hero WHERE number = 8 FOR UPDATE;
number为8的值加上排它行锁
UPDATE
UPDATE hero SET country = '汉' WHERE number = 8;
没有更新二级索引的值,所以和select * from xx for update一致
UPDATE hero SET name = 'cao曹操' WHERE number = 8;
更新了二级索引,所以需要在二级索引上加上行锁排他锁
DELETE操作
和update一致
使用主键进行范围查询
SELECT ... LOCK IN SHARE MODE
SELECT * FROM hero WHERE number >= 8 LOCK IN SHARE MODE;
number值为8的创建一个S形行锁,number值大于8的创建一个S形的next-key锁
SELECT * FROM hero WHERE number <= 8 LOCK IN SHARE MODE;
当number等于15的时候不满足number<=8,所以查询条件结束,但是会在number=15这一列上加入next-key Lock锁,整体表现形式如下:
SELECT ... FOR UPDATE
和SELECT ... LOCK IN SHARE MODE加锁过程一致,加锁类型为x形的next-key Locks
UPDATE
如果语句没有更新二级索引,则和select for update加锁情况一致
如果语句更新了二级索引,则需要在二级索引上加入排他行锁
UPDATE hero SET name = 'cao曹操' WHERE number >= 8
UPDATE hero SET name = 'cao曹操' WHERE number <= 8;
会对number为1,3,8,15加上next-key Lock锁,但是在二级索引上,不会对id=15这一列加锁
DElETE
和update加锁情况一致
使用唯一索引进行等值查询
SELECT ... LOCK IN SHARE MODE
- 如果记录不存在,则需要在其下一个记录中加上next-key Lock锁,不需要对聚族索引加锁
- 如果记录存在,则加共享形行锁,并且在聚族索引上加锁对应的共享行锁 SELECT ... FOR UPDATE
- 和SELECT ... LOCK IN SHARE MODE一致,加锁类型为排他锁 UPDATE ...和DELETE ... 和SELECT ... FOR UPDATE加锁顺序和类型一致,如果更新的列中有二级索引,需要首先在二级索引中X型行锁
使用唯一索引进行范围查询
SELECT ... LOCK IN SHARE MODE
SELECT * FROM hero FORCE INDEX(uk_name) WHERE name >= 'c曹操' LOCK IN SHARE MODE;
SELECT * FROM hero WHERE name <= 'c曹操' LOCK IN SHARE MODE;
对于使用普通二级索引进行等值查询的情况
ALTER TABLE hero DROP INDEX uk_name, ADD INDEX idx_name (name);
SELECT ... LOCK IN SHARE MODE
SELECT * FROM hero WHERE name = 'c曹操' LOCK IN SHARE MODE;
- 对全部name = 'c曹操'的二级索引加上S形的next-key Lock锁
- 对最后一个name值为'c曹操'的二级索引记录的下一条二级索引记录加gap锁。
DETELE,UPDATE,SELECT ... FOR UPDATE
三个情况和SELECT ... LOCK IN SHARE MODE一致,不过加锁类型为X型排他锁
全表扫描的情况
SELECT * FROM hero WHERE country = '魏' LOCK IN SHARE MODE;
因为country这个列没有加上索引,所以会进行全表扫描,存储引擎没扫描到一个数据都会为这个记录建立起来S型的next-key Lock锁,并且把这个数据返回给Server层,由server层判断,如果不符合就释放锁(在可重复读隔离级别下不会释放锁,所以聚集索引全部记录都会被加锁,并且在事务提交前不会被释放)
SELECT ... FOR UPDATE
加锁顺序同上,加锁类型为X形行锁
UPDATE
加锁顺序同上
DELETE
和UPDATE一致