MySQL高阶之锁原理篇

update流程图

update t set c=‘曹操’ where id = 1;
在这里插入图片描述

主键等值更新速度最快 加行锁
主键范围更新需要加间隙锁 防止出现幻读
如果不应用索引需要加全表锁+间隙锁

Mysql锁的分类

锁的粒度

  • 全局锁:锁住整个mysql实例 此时只能读
  • 表级锁:锁定某一个表
  • 行级锁:对某一行数据加锁。只有InnoDB引擎支持

锁的功能

  • X锁(写锁):排它锁
  • S锁(读锁):共享锁

锁的类型

全局锁

  • 锁住:flush tables with read lock;
  • 释放锁:unlock tables; 会话断开会自动解锁

表级锁

  • 读锁:SessionA加锁后,表处于只读状态,其他的Session只能读,不能进行写,sessionA也只能读不能写,其他session也可以加读锁
  • 加锁:lock table {表名} read
  • 释放锁:unlock tables

写锁

  • 加锁之后当前会话可读可写,其他会话不能读也不能写
  • 加锁:lock table {表名} write
  • 释放锁:nulock tables

演示

CREATE TABLE mylock ( 
id int(11) NOT NULL AUTO_INCREMENT, 
NAME varchar(20) DEFAULT NULL, 
PRIMARY KEY (id)
);
INSERT INTO mylock (id,NAME) VALUES (1, 'a');
INSERT INTO mylock (id,NAME) VALUES (2, 'b');
INSERT INTO mylock (id,NAME) VALUES (3, 'c');
INSERT INTO mylock (id,NAME) VALUES (4, 'd');

读锁演示

在这里插入图片描述

写锁演示

在这里插入图片描述

元数据锁

当开启事务之后执行对某一个表的查询操作,此时会对表加元数据锁,此时不允许其他事务对其表结构进行修改。

1、session1: begin;--开启事务
  select * from mylock;--加MDL读锁
2、session2: alter table mylock add f int; -- 修改阻塞
3、session1:commit; --提交事务 或者 rollback 释放读锁
4、session2:Query OK, 0 rows affected (38.67 sec)  --修改完成
 Records: 0 Duplicates: 0 Warnings: 0

自增锁

mysql支持自增主键,生成的主键不能重复,所以需要有自增锁来保证生成的主键是唯一的。

行锁

只有InnoDB引擎支持 行级锁是由引擎层来实现的 InnoDB行锁是通过给索引上的索引项来加锁的 因此还有通过索引条件检索的数据才会使用到行锁,否则使用的是表锁.即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引

记录锁

只锁定某一个表的某一条记录。也有X锁和S锁
加锁的时机:根据主键等值查询并且命中一条记录

update t set c = 3 where id = 1	--x锁
delete from t where id = 1	--x锁
select * from t where id = 1 for update --x锁
select * from t where id = 1 lock in share mode -- s锁

间隙锁

记录和记录之间的缝隙值,当加间隙锁后不允许间隙值的插入
例如 三条记录:id:1 5 10
1和5之间的缝隙值就是2 3 4,此时不允许插入2 3 4 的记录

select * from t where id >=1 and id <10 for update --x锁
#加锁的时机:通过主键更新或查询询数据没有命中任何记录时加间隙锁

临建锁

  • 记录锁+间隙锁:默认的加锁方式。根据主键范围更新的操作加临建锁.
  • 如果主键查询命中了一条记录会退化为记录锁
  • 如果主键查询没有命中任何记录会退化成间隙锁

意向锁(Innodb表级锁)

当对表加表级锁,需要判断该表是否有行锁的存在,如果存在是无法加锁的。此时需要快速知道表上是否有行锁的存在,引入了意向锁,会在加行锁后,在表上新增一个标记位,标志该表有行级锁。

插入意向锁

在执行insert操作后,事务还没有提交之前,需要锁定插入行的主键的值。防止其他事务方式该主键

实战面试题

面试官:以下这条语句执行过程当中加入了哪些锁?

delete from t where id  =10

看似简单 其实全是坑!!!如果你连前提都没有提出疑问,那么不好意思,请回家等通知!!!
百分之80的人会说,加写锁
这个答案对吗?说不上来。即可能是正确的,也有可能是错误的,已知条件不足,这个问题没有答案。必须还要知道以下的一些前提,前提不同,能给出的答案也就不同。要回答这个问题,还缺少哪些前提??

条件?

  • 前提一:id列是不是主键?
  • 前提二:当前系统的隔离级别是什么?
  • 前提三:id列如果不是主键,那么id列上有索引吗?
  • 前提四:id列上如果有二级索引,那么这个索引是唯一索引吗?
  • 前提五:两个SQL的执行计划是什么?索引扫描?全表扫描?

组合1:id主键 + RC隔离级别

因为id是主键,并且sql语句执行命中了一条记录,此时会加一把临建锁,但是由于只命中了一条记录,因此会退化成记录x锁。因此只会加一次x锁
在这里插入图片描述

组合2:id唯一索引(辅助索引) + RC隔离级别

  • 第一次锁:由于id是唯一索引(非主键索引),此时根据索引找到一条记录,加x锁(行锁)
  • 第二次锁:找到id-10的记录之后,需要通过id=10这条记录找到主键,假设该例子主键是name字段,那么需要通过name主键找到记录(回表),此时加x锁(行锁)
  • 总结:加两次x锁
    在这里插入图片描述

组合3:id非唯一索引+RC隔离级别

  • 和组合2有点类似.
  • 第一次锁:锁住id=10的所有记录 加行锁
  • 第二次锁:通过找到的记录的主键回表查询具体对的某一条数据行,加行锁
    在这里插入图片描述

组合4:id无索引+ RC隔离级别

分析:犹豫id字段没有索引,那么mysql只能选择走主键索引,进行全表扫描.如图 满足条件的有两条记录,但是所有的记录行都被锁住了.但是mysql server层对其进行了优化 当所有记录行加x锁之后会进行判断,此时id=10的只有两条记录符合,因此会释放其他不满足条件的记录行,这是一个加锁的过程,但是这个操作也是不能省略的.

在这里插入图片描述

组合5:id主键+RR

和组合1一致

组合6:id唯一索引+ RR隔离级别

和组合2一致

组合7:id非唯一索引 + RR

间隙锁+记录锁+回表x锁

  • 分析:RC和RR的区别在于 RC允许幻读的存在 RR不允许幻读的存在 关键就在于这 间隙锁就是RR隔离级别防止幻读的关键所在
  • 何为幻读?我一个事务同一条查询sql执行两次,得到的是不同的结果,为什么?因为我第二次读可能读到了别人修改过记录的值
  • gap锁:间隙锁 如图 开启一个事务读取id=10的记录,理想应该是两条记录,但是中途有其他事务也插入了一条id=10的记录进来,我再次去读就读到三条记录,为了防止这种情况,gap锁应运而生。他的作用就是在临界旁边加锁,此时id=6至id=10之间是不允许插入数据的,从而杜绝了幻读的发生
  • 加锁过程:通过id非唯一索引定位到第一条满足查询条件的记录,加记录上的x锁,然后加gap锁(间隙锁),然后通过辅助索引找到主键索引再次加x锁,然后返回;然后读取下一条,重复执行,直到进行到第一条不满足条件(id<>10)的记录,此时不需要再加锁,但是仍然要加gap锁
    在这里插入图片描述

组合 8:id无索引+RR

  • 所有记录加行锁+全表间隙锁
  • 无索引只能进行全表扫描,每条记录加x锁,全表加间隙锁,此处只有6条记录,一共需要6个记录锁,7个GAP锁。试想,如果表上有1000万条记录呢?
    在这里插入图片描述

组合9:Serializable串行化隔离级别

串行执行 效率很低 不考虑

死锁

产生原因:两个事务互相持有对方请求的资源
死锁处理:MySql自动检测死锁,并且会自动回滚代价小的事务,从而解决死锁
避免:尽快释放资源,减少资源持有的时间。

在这里插入图片描述
在这里插入图片描述
如何避免死锁呢?

  • MySQL默认会主动探知死锁,并回滚某一个影响最小的事务。等另一事务执行完成之后,再重新执行该
    事务。

1、注意程序的逻辑
根本的原因是程序逻辑的顺序,最常见的是交差更新
Transaction 1: 更新表A -> 更新表B
Transaction 2: 更新表B -> 更新表A
Transaction获得两个资源
2、保持事务的轻量
越是轻量的事务,占有越少的锁资源,这样发生死锁的几率就越小
3、提高运行的速度
避免使用子查询,尽量使用主键等等
4、尽量快提交事务,减少持有锁的时间
越早提交事务,锁就越早释放

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值