mysql5.7的锁:乐观锁/共享锁、互斥/排他锁、意向锁、记录锁、行锁/表锁、间隙锁、临界锁、插入意向锁、自增锁、空间索引预测锁、隐式锁

从锁的模式来区分:Shared Locks共享锁(行锁)、Exclusive Locks(行锁)

意向锁区分: Intention Shared Locks意向共享锁、Intention Exclusive Locks意向排它锁

从锁的算法区分:Record Locks记录锁、Gap Locks间隙锁、Next-Key Locks临界锁

一致性读/快照读

【锁定读(Locking Reads)/LBCC】

当前读分类:select for update、select .... lock in share mode、update insert delete、串行化的级别,当前读的这种方式就称为LBCC,全称为 lock-based Concurreny Control 基于锁的并发控制。

【共享锁和独占锁】

共享锁,简称S锁,其他线程可以继续获取共享锁。

独占锁,简称X锁,只允许获取一次,不容许其他线程获取独占锁。所以像select for update就会独占线程锁死这行。

insert    一般不加锁,隐式锁

update    X锁:   不更新主键的情况下,就地更新,更新主键的情况下。

               X锁:   更新主键的情况下,先执行delete操作,  (隐式锁:)再执行insert操作。

delete   X锁

组合型 X 独占锁 IX 意向独占锁 S 共享锁 IS 意向共享锁
表锁
行锁 -- --

表锁级别的兼容性,比如X独占锁与X独占锁就不兼容:

兼容性 X 独占锁 IX 意向独占锁 S 共享锁 IS 意向共享锁
X 独占锁 不兼容 不兼容 不兼容 不兼容
IX  意向独占锁 不兼容 兼容 不兼容 兼容
S 共享锁 不兼容 不兼容 兼容 兼容
IS 意向共享锁 不兼容 兼容 兼容 兼容

怎么给表加锁?

Lock table t Read;  表级别S锁

Lock table t Write;  表级别X锁

而innodb已经有行锁了,所以以上这种手动加锁没太大意义,除非崩溃时恢复数据的特殊时期。

InnoDB中的表级锁

1、表级别的S锁、X锁、IS锁、IX锁、

2、元数据锁(Metadata Locks, 简称MDL)Server

3、AUTO-INC锁,自增主键锁,解决主键自增列避免产生重复自增主键的锁

InnoDB中的行级锁

1、Record Locks(官方名称是:Lock_Rec_Not_Gap)   记录锁

2、Gap Locks(官方名称:Lock_Gap) 间隙锁,对某条记录的前后,也就是两条记录之间的间隙加锁,不对行本身加锁。

3、Next-Key Locks(官方名称:Lock_ORDINARY) 临界锁

4、Insert Intention Locks  插入意向锁

5、隐式锁

【间隙锁例子】

假设我在teacher表中为name这一列建立了一个索引idx_name:

事务1 事务2

select * from teacher order by name;     --  按照idx_name列的索引排序,查出来的数据是这样的:

 
begin;     -- 开启事务  
update teacher set domain = 'Spring' where name = 'James';   -- 这行id是9,此时事务尚未提交,mysql会对id=9的行前后都加间隙锁,不允许此行前后插入数据。  
  begin;   -- 开启事务
  insert into teacher values(22, 'Jahes', 'docker');  -- 在name='James'行的后面添加新行,此sql会阻塞住, insert失败,因为事务1的间隙锁已开启。如果长时间阻塞住(也就是事务1长时间不提交),会报错:ERROR 1205(HY000): Lock wait timeout exceeded; try restarting trasaction;
  inset into teacher values(7, 'King', 'docker');  -- 根据idx_name索引列的B+tree排序规则,由于King值已存在,所以需要将同name值的id进行排序,此insert语句中的id 7 要排到id=15的前面去,刚好落在间隙锁范围,同样阻塞住,insert失败
  insert into teacher values(70,'King','docker');  -- 此时id 70落在id=15的后面,避开了间隙锁,insert成功
   

【临界锁例子】

临界锁就是记录锁和间隙锁的合体,也就是不仅像记录锁一样锁住行本身,还像间隙锁一样锁住了行的前后间隙,不允许insert。

那么可重复读的事务隔离级别下,innodb引擎就是使用了临界锁进行搜索和索引扫描,来避免幻读的情况。

【插入意向锁例子】

根据间隙锁的例子,在事务2中insert数据需要别的事务进行提交或回滚才允许事务2insert的情况,那么开启一个插入意向锁,类似于排队机制。

【隐式锁例子】

一个事务可以不显式的去加锁,但是别的事务在对新加的记录准备加X或S锁的时候,首先会去检查trx_id这个隐藏列,然后进行相关的锁和事务判断(这里参考下面的锁的内存结构)。

假如A事务先insert一条新记录,然后B事务去对这个新记录进行S或X锁的时候,B事务会帮助A事务在内存中生成锁结构,B事务自己再生成一个锁结构,然后把自己置为等待状态。

隐式锁的好处是延迟锁的生成,如果一个insert语句是不需要锁的,但是如果多条sql插入相同id,那么隐式锁的存在就发挥了作用,就像并发编程里的偏向锁升级成了轻量锁。

【锁的内存结构】

每条数据记录的锁结构都包含两个信息:事务信息 = t1;  是否等待 = false;  false表示该锁是当前事务创建的,当前事务对获取该锁不需要等待。

当事务1提交之后,事务1会去唤醒等待该行数据记录的其他锁,相应的其他锁结构也包括两个信息:事务信息 = 2;是否等待 = true;  依次唤醒所有锁和事务。

如果是独占锁,那么一次唤醒一个,如果是共享锁,那么一次可以唤醒多个。

同一个事务,针对同一个数据页面,同一个加锁类型,才会公用一把锁,这三者只要任何一个不同,都会生成一把新的锁,但是锁结构相同。

【锁的开启和查看】

show variables like 'innodb_status_output_locks';    -- 默认是OFF关闭状态,需要开启: set global innodb_status_output_locks = ON;   

show engine innodb status\G    -- 查看innodb加锁的情况就会变得非常非常详细:

如果你的sql中加锁很慢的情况,就可以用这种方法来判断事务之间是否出现了严重的锁争抢情况。

【死锁】

一般来说只要有并发和死锁的共同加持情况,都会有死锁的身影,两个或两个以上的进程在执行过程中,由于竞争资源或者通讯问题造成彼此阻塞现象,若无外力作用而无法推进解决,此时称为死锁或者死锁状态。

产生死锁的必要条件:

1)互斥。进程对于分配给他的资源是排他性使用。

2)请求和保持。我已经通过请求拿到了一个资源,但是我又提出了一个新资源的请求 ,但是更多的资源我拿不到,同时对于我自己已有的资源也不放手,所以这叫请求与保持。

3)不剥夺。对于进程已经持有的资源在使用完之前不剥夺。

4)环路等待。

死锁例子:

事务1 事务2
begin;    -- 开启一个事务  
select * from teacher where number = 1 for update;   -- 加了一个行上的X锁  
  begin;
  select * from teacher where number = 3 for update;  -- 加了一个行上的X锁
select * from teacher where number = 3 for update;  -- 此时会阻塞,因为锁被事务2抢走

 

 

select * from teacher where number = 1 for update;   -- 尝试加锁,但是失败:发生死锁,会报错:

ERROR 1213 (40001) : Deadlock found when trying to get lock; try restarting trasaction

发生死锁之后,mysql自动介入结束当前事务

此时上面的 3 for update会执行下去了,不再阻塞,因为事务2已经结束了。  
commit;        -- 此时rollback 或者 commit都可以结束事务。  
show engine innodb status\G    -- 此时将会看到最近一个死锁产生的情况  

 

end.

posted on 2021-07-26 10:26  梦幻朵颜  阅读(916)  评论(0编辑  收藏  举报