锁
Q:为什么要用锁?
1、操作金额、红包、用户余额、订单状态的时候
2、判断一条记录存不存在,不存在的话执行操作
3、减商品库存的时候(并发大的时候,很容变负数)
myisam 表锁
innodb 行锁
共享锁(S锁):假设事务T1对数据A加上共享锁,那么事务T2可以读数据A,不能修改数据A。
select * from table where id = ? lock in share mode
排他锁(X锁):假设事务T1对数据A加上共享锁,那么事务T2不能读数据A,不能修改数据A。
select * from table where id = ? for update
update、delete、insert
记录锁:行锁 (锁索引)
gap锁:间隙锁,锁定一个范围,但不包含记录本身
next-key锁:Gap Lock+Record Lock,锁定一个范围、索引之间的间隙,并且锁定记录本身;目的是为了防止幻读
MVCC
事务隔离机制
name | 脏读 | 不可能重复读 | 幻读 |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable read | × | × | √ |
Serializable | × | × | × |
这里一定要区分 不可重复读 和 幻读:
不可重复读的重点是修改:
同样的条件的select, 你读取过的数据, 再次读取出来发现值不一样了
例子:A事务结算时,查询金额足够刚好500,去结算流程,此时B事务刚好减去金额200,A事务在结算最后扣除金额时发现不够
幻读的重点在于新增或者删除:
同样的条件的select, 第1次和第2次读出来的记录数不一样
例子:A事务插入一条name='xiao’的记录,判断不存在,继续下面的逻辑;此时B事务插入一条name="xiao"的记录并提交,A事务就报错了
如果没有MVCC的话,实现RC和RR的方式分别是:
RC:更新的时候加锁,其他事务任何读都要等待
RR:读的时候加锁,其他事务只能读不能更新
MVCC相关原理
Mutli-Version Concurreny Control,多版本并发控制,读不加锁,读写不冲突。应用于 Read Commited 和 Repeatable read 两个事务隔离级别。
快照读: 普通的select,不加锁,读取记录的可见版本。
当前读:select…lock in share mode/for update、update、insert、delete,读取记录最新版本,并加锁。
MVCC的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的。所以我们先来看看这个三个point的概念
地址:https://blog.csdn.net/SnailMann/article/details/94724197
隐藏字段
undo日志
undo log主要分为两种:
insert undo log
代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
比如一个有个事务插入persion表插入了一条新记录,记录如下,name
为Jerry, age
为24岁,隐式主键
是1
现在来了一个事务1对该记录的name做出了修改,改为Tom
来了个事务2修改person表的同一个记录,将age修改为30岁
Read View
Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
我们可以把Read View简单的理解成有三个全局属性
trx_list
一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID
up_limit_id
记录trx_list列表中事务ID最小的ID
low_limit_id
ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1
首先比较DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到DB_TRX_ID 所在的记录,如果大于等于进入下一个判断
接下来判断 DB_TRX_ID 大于等于 low_limit_id , 如果大于等于则代表DB_TRX_ID 所在的记录在Read View生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断
判断DB_TRX_ID 是否在活跃事务之中,trx_list.contains(DB_TRX_ID),如果在,则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在Read View生成之前就已经Commit了,你修改的结果,我当前事务是能看见的
在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;
在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View
RC的情况:
事务A | 事务B |
---|---|
开启事务 | 开启事务 |
快照读(无影响)查询金额为500 | 快照读查询金额为500 |
更新金额为400 | × |
提交事务 | × |
- | select 快照读金额为400 |
- | select lock in share mode当前读金额为400 |
RR的情况:
事务A | 事务B |
---|---|
开启事务 | 开启事务 |
快照读(无影响)查询金额为500 | × |
更新金额为400 | × |
提交事务 | × |
- | select 快照读金额为500 |
- | select lock in share mode当前读金额为400 |
案例分析
select * from t1 where id = 10
delete from t1 where id = 10
组合一:id主键
组合二:id唯一索引
组合三:id非唯一索引+RC
组合四:id非唯一索引+RR
组合五:id无索引+RC
组合六:id无索引+RR
Q:select * from t1 where id > 10 for update ?
Q:什么场景下用共享锁、排他锁?
共享锁:我查完之后我自己不改,我也不希望别人去改。比如:查询商品价格并且生成订单
排他锁:我查完之后我自己还要改,不允许别人读。比如:修改订单状态,修改余额
热门评论
这一篇写的太好了。事务隔离级别那写的有点欠缺觉得。