1.概述
事务的隔离性由锁来实现
2.Mysql并发实务访问相同记录
并发事务访问相同记录的情况大致可以划分为3种:
2.1 读-读
2.2 写-写
锁结构和事务是一一对应的,有几个事务,就会生成几个锁结构,如果该记录还有别的事务要进行操作,会对别的事务也建立锁结构!
锁的生成
事务T1 提交之前
事务t1 提交之后
小结
2.3 读-写或者写-读
2.4 并发问题的解决方案
(1)读MVCC ,写加锁
(2)读和写都加锁
- 场景:读取的数据必须是最新数据
- 脏读问题:B事务先对记录x进行修改,然后A事务去读取记录x,只要B还没提交,A就不能读取记录x;A读不到B未提交的数据
- 不可重复读:A事务先读取读记录x,然后B事务想要对x进行修改,此时B就必须等待A事务结束,才能写;
- 幻读:事务A读取了一个范围的记录,然后B事务向该范围内做了增删记录
小结
3.锁的不同角度分类
https://www.bilibili.com/video/BV1iq4y1u7vj?p=174
3.1 从数据操作的类型划分:读锁、写锁
额外说明:
- 本质上来说是没有读锁和写锁的,只有排它锁和共享锁;
- 读操作而言默认是共享锁,但是也可以给读操作上排它锁
- 写操作就必须是排它锁
0.行级别x锁 和 s锁的兼容性问题
不管是现有读锁还是写锁,只要有写锁参与都会被阻塞;
1.锁定读
读操作加S锁
读操作加X锁
演示1. 两个共享锁
演示2: 先加共享锁,后加排它锁
演示3: 先加排它锁,后加共享锁
8.0 新特性
2. 写操作
宏观来看,写操作就是加X锁 ;
细节来看,update和delete操作是真正的加排他锁;insert操作只是加隐式锁;
3.2 从数据操作的粒度划分:表级锁、页级锁、行锁
锁粒度越小,并发度越高;但是同时,管理锁消耗的资源就越多!
1. 表锁
(1)表级别S锁和X锁
InnoDB 下的元数据锁
InnoDB 下表锁
如何给表上锁演示
表锁下的 读写并发演示
上面所说的自己,就是给表上锁的事务;
(1) 表级别读锁
(1) 表级别写锁
(2)意向锁
意向锁是表锁
意向锁要解决的问题
- 如果事务A给数据表中某些记录上了共享锁,会自动给该数据表添加意向共享锁
- 如果事务A给数据表中某些记录上了排他锁,会自动给该数据表添加意向排他锁
如何添加意向锁
举例
意向锁IX和表级S锁互斥
事务A给表teacher的某行上了排它锁,自动给表上了意向排它锁IX,此时别的事务想要给此表上表级读锁和表级写锁都会被阻塞!
总结
- 意向锁是表级锁,是InnoDB引擎自动生成的;
- InnoDB支持多粒度锁,特定场景下, 表级锁(意向锁)和行级锁共存;
- 意向排它锁
IX
,意向共享锁IS
- 意向锁之间是不互相排斥的;
- 意向锁和行级锁也是不互相排斥的(和第二条一个意思=> 意向锁和行锁共存)
-
表级锁 和 意向锁 之间的排斥规则 和读写锁的排斥规则是一样的
- 意向锁在保证并发性的前提下,实现了行锁和表锁共存 并且 满足事务隔离性 的要求
(3)自增锁
https://www.bilibili.com/video/BV1iq4y1u7vj?p=176&spm_id_from=pageDriver
自增约束字段的插入演示
三种插入模式讲解
针对自增锁并发性低下的三种锁定模式
(1)传统模式
传统模式下,只要是insert 语句就要持有AUTO-INC锁才能执行;否则排队阻塞;
(2)连续锁定模式
- 这种模式优化了
插入已知行数
的场景;
- 对于insert xx select xx from 这种仍然需要AUTO-INC锁;
- 而对于插入行数已知的insert 语句来说,在执行的时候,直接获取行数,表示要插入这么多行先占位!不需要等数据全部插入进来,占好位置后就可以将AUTO-INC锁释放了
(3)锁定模式
(4) 元数据锁(MDL锁)
2.行级锁
- 只有InnoDB支持行级锁
(1)记录锁
记录锁的特点就是锁住表中已有的记录;
(2)间隙锁
由于是间隙锁,因此间隙读锁、间隙写锁没有本质区别;不影响别的事务继续加gap锁:
这里上锁并非是给某个值上锁,而是给区间(3,8) 上锁;
间隙锁会阻碍insert:
上面锁住了(3,8),因此这里插入6是插不进去的
如果在最后一行后面上锁,锁区间就是最后一行到正无穷:
间隙锁可能会造成死锁
如下图所示:
第一步:在事务A中,给(3,8)区间上间隙读锁;
第二步:在事务B中,给(3,8)区间上间隙读锁;
第三步:在事务B中,在(3,8)区间插入一条记录,被阻塞
第四步:在事务A中,在(3,8)区间插入一条记录,报错,错误日志:产生死锁;
- 事务A持有id=1的写锁
- 事务B持有id=2的写锁
- 事务A想要获取id=2的写锁,被阻塞
- 事务B想要获取id=1的写锁,被阻塞
死锁解决方案:
- 等待超时
- 让步,让最后一个产生死锁的事务回滚(即事务结束),从而事务B可以顺利执行;
(3) 临键锁(Next-key_lock)
https://www.bilibili.com/video/BV1iq4y1u7vj?p=178
间隙锁是开区间,临建锁是闭区间
临建锁的写法:
(4) 插入意向锁
插入意向锁互不排斥:
3.页锁
3.3 从对待锁的态度划分:乐观锁、悲观锁
1.悲观锁
2.乐观锁
(1)版本机制实现乐观锁
update 操作会先读取该记录
事务A:
- 先读,读的时候版本是1
- 然后进行update,update的时候还是1,说明在读后没有别的事务修改过他
- 做完修改后,将version + 1
事务B过来:
- 读是2
- 进行修改,version也是2,则可以进行修改。
读和写之间没有别的事务修改它,相当于保证了读和修改的原子绑定
针对的场景
事务A
事务B
事务A
- 修改 update xx where version = 1 发现没有了,修改失败;此时再次查询,发现version变成2了,再对version = 2进行修改
(2) 时间戳机制
从秒杀案例2看出:
第一次是读,第二次update也是读,也就是说必须保证写的时候版本和读的时候一致才能进行修改;两次读!!!
读写分离场景: 读从机,写主机
强制读主机,读写保持一致!
高并发场景
- 1、多个事务查询某条记录 version = 1;
- 2、事务A,进行修改 version =2;
- 3、其他事务再进行修改,发现version不一致了,因此修改失败
3.4 按照加锁方式进行分类
https://www.bilibili.com/video/BV1iq4y1u7vj?p=180
1.隐式锁
场景1:Insert
- 事务A,插入一条记录,此时是不加锁的;还未提交
- 事务B进来了,进行查询,此时会对A插入的记录进行上锁,因此事务B无法访问到;
特点:懒加载;只有被别的事物要访问的时候才会上锁
目的:防止事务A插入的数据在还未提交时,被别的事务访问到;
场景2:插入意向锁