锁类型
表级别、行级别
共享锁(S 读锁)
排他锁(X 写锁)
1.读锁与读锁相互兼容
2.写锁与任何锁(读锁及写锁)都不兼容
表级别
表级别 innodb 还支持意向锁:
意向共享锁(IS 意向读锁)
意向排他锁(IX 意向写锁)
1.意向锁之间相互兼容
2.意向锁于非意向锁之间遵循 读写锁的兼容模式
3.意向锁含义:想要获取某张表的某几行的 锁,需要在行的上一级 表上加相应的意向锁,设计目的是在一个事务中揭示下一行将被请求的锁类型(不是很理解???)
select显示加锁:
1.select … for update; —显示加写锁
2.select … lock in share mode; —显示加读锁
锁粒度
表锁
锁整张表 mysql alter
行锁
事务
事务隔离级别
读未提交 read uncommitted
读已提交 read committed (锁定一致性读)
可重复度 repeatable read (非锁定一致性读)
可串行化 serializable
多版本并发控制 mvcc(快照数据)
1.行锁实现方式
2.只在 读已提交、可重复度 两种隔离级别生效,因为读未提交无关事务,能看到所有修改;可串行化隔离级别最高,在每一行上加锁,无操作空间
3.innodb 每条记录最后都额外有两个字段:创建版本、删除版本(存的是版本号,每开启一个事务 版本号都会自增,保证这个版本号对于每个事务都有唯一性,且能识别先后顺序)
4.可重复度(repeatable read)隔离级别下单 mvcc 实现:
select:获取创建版本在当前版本之前(小于或者等于),删除版本在当前版本之后(大于)或者未定义删除版本号的记录,保证这条记录在当前事务开启之前就已被创建,且当前事务开启的时候还未被删除或修改
insert:插入记录行的版本号设置成当前事务的版本号
update:愿记录的删除版本号设置成当前版本号,再插入一条修改后的最新的记录,创建版本为当前版本号,删除版本号不定义
delete:设置删除记录的删除版本号为当前版本
左右两事务都未提交,左边分别 insert 和 update 表test,但右边事务中select始终得到的是食物开启前的数据,因为是可重复读,mvcc控制的效果,当两边事务都提交后,select出来的结果就一致了
5.通过可重复读的select规则,读已提交的select规则不难推测只要版本号的判断上松一点即可,获取删除版本号为定义的就是最新的记录,及其他事务操作后的记录就也能被当前事务获取到了
6.mvcc 极大的提高了数据库的并发性,因为查询无需等待锁的释放,无需占用和等待表上的锁
锁定一致性读
1.自增长字段:
当表中存在自增长字段,两个事务同时插入一条记录
两边的事务都未提交,但两边都获取到了正确的自增长id,即右边事务在未提交情况下获取到了左边事务未提交情况下都id。
因为自增长列有一个特殊的表锁机制:auto-inc locking。这个锁并不是在事务提交时释放,而是在有自增长值的sql语句执行完后立即释放
所以即使左边的事务rollback了,插入操作被取消了,但id 的自增长结果还是被保留了
2.auto-inc locking 机制也是提高了自增长值的插入性能,避免多个事务同时插入数据是互相等待对方锁的释放
锁的算法
1.record lock:单个行上的锁
2.gap lock:间隙锁
3.next-key lock:临间锁(record lock + gap lock)
如果是聚簇缩影(唯一主键),会降级为record lock
如果是辅助索引,repeatable read 隔离级别下会添加间隙锁
例:
Indextest 表中 a为主键,b为辅助索引,此时针对辅助索引b分为了(1, 3],(3, 6],(6, 8],这些区间(等于的区间放在前面还是后面有不同的模式),如果事务 T1 锁住了b=3 这条记录,那相应的 b=3 所在的前后两个区间( (1, 3],(3, 6] )都会被锁住,事务 T2 想要在这些范围内加锁是就需要等待 T1 事务结束,释放锁。
如图,当左边事务提交后,右边事务才活渠道 数据;
因为repeatable read 隔离级别下
采用 next-key lock算法,read committed 隔离级别仅采用 record lock算法,所以关闭如上功能只要吧隔离级别改为 read committed 即可,
隔离级别 |
Mvcc |
锁算法 |
Read uncommitted |
||
Read committed |
有 |
record lock |
Repeatable read |
有 |
Next-key lock |
Serializable |
|
死锁
如图:
T1,T2 分别锁上 a = 1,a = 3 的记录,T1在获取 a = 3时由于 T2 未释放,T1 阻塞(图上是因为T2报死锁异常,T2结束释放了锁才看到了返回,此处不详细描述了),T2再去获取 a = 1的记录时,由于 T1持有了 a = 1的锁,并没有释放,再阻塞着,所以T2 等待 T1 释放 a = 1 的锁,也阻塞了,这样 T1、T2 都在等待对方释放锁而阻塞,就死锁了,所以报死锁异常了