一文打尽MySQL中各种锁
  655OvFktQQaY 2023年11月02日 40 0

希望通过本篇回答以下问题:

  • 不同锁的适用场景,加锁、释放锁的方式?
  • 不同锁在具体情况下会锁定哪些数据?
  • 什么样的生产环境下会产生死锁,如何解决?

引子

锁的引入通常是为了处理并发问题,保证数据安全。

MySQL中有哪些锁? 需要分不同维度来回答。

  • 按照使用方式上:排它锁、共享锁
  • 按照加锁粒度上:全局锁、表级锁、行级锁
  • 按照思想上:乐观锁、悲观锁

按使用方式划分

共享锁

概念 Share lock,也叫读锁。当对象被锁定时,允许其他事务读取该对象,也允许其他事务从该对象上获取共享锁,但是不能写入。

适用场景 说是优势更合适:允许多个事务同时读数据,提高系统的并发读能力。

加锁、释放锁方式

-- 加锁的两种方式
-- 方式1
select ... lock in share mode;
-- 方式2
select ... for share;

-- 释放锁的方式
-- 1. 事务提交 commit;
-- 2. 事务回滚 rollback;
-- 3. 断开连接,会释放该连接中所有的锁;

共享锁和排它锁不讨论其实际会锁定那些行,这个放到粒度里去讨论。

排它锁

概念 Extention Lock,写锁、独占锁。阻塞其他事务对当前对象的读写操作。

适用场景

  • 防止并发修改
  • 保证唯一性约束
  • 避免幻读

加锁、释放锁方式

-- 加锁
select ... for update;

-- 释放锁的方式
-- 1. 事务提交 commit;
-- 2. 事务回滚 rollback;
-- 3. 断开连接,会释放该连接中所有的锁;

按粒度划分

全局锁

概念 对整个数据库实例加锁,最大粒度的锁。

适用场景 谨慎使用,必须绝对必要才使用,以下情况不一定非要用全局锁,除非你需要确保完全无外界写入操作干扰。

  • 全库备份
  • 全库导出

加锁、释放锁方式

-- 方式1
-- 加锁
flush tables with read lock; -- FYWRL 执行完毕后,整个数据库就处于只读状态了。
-- 释放锁
unlock tables;

-- 方式2
-- 加锁
SET GLOBAL READ_ONLY = 1;
-- 释放锁
SET GLOBAL READ_ONLY = 0;

锁定的数据 锁定数据库内所有的表,上的是读锁。

表级锁

包含两种:表锁和元数据锁。

表锁

概念 对整张表上锁,包括读锁和写锁,需要显式加锁和释放。

适用场景

  • 并发要求低的场景,表锁相对来说开销小。
  • 批量数据写入时,也可以使用表锁保证数据的一致性和完整性。
  • 防止死锁
  • 高并发读的场景

加锁、释放锁方式

--默认加写锁
lock tables tablenmae; 
-- 加写锁
lock tables tablename write;
-- 加读锁
lock tables tablename read;
-- 释放锁
unlock tables;

锁定的数据 锁定表内的所有行。

MDL元数据锁

Metadata Lock,简称MDL,为了防止对一个正在使用的数据库对象(例如,表和触发器)进行结构性更改。 上MDL读锁时,其他线程能够进行增删改查。 上MDL写锁(DDL操作时触发)时,对读锁和写锁都互斥,用来保证变更表结构操作的安全性。 但是,DDL语句未必一定阻塞DML语句,这里不展开说明。

加锁、释放锁方式 并非显式加锁和释放锁,而是在访问表时自动加上

锁定的数据 一个数据库对象:一张表、视图、存储过程或其他数据库对象。

意向锁

概念 Innodb引擎支持多粒度锁定,允许行锁和表锁共存,为了快速判断表中是否存在行锁,InnoDB推出了意向锁。意向锁本身是一种表锁。 通常有两种类型:

  • 意向排他锁(IX):接下来要在表中某些行上加排它锁
  • 意向共享锁(IS):接下来要在表中某些行上加共享锁

适用场景 实际是好处:

  • 减少锁冲突:在事务开始时声明其意向,帮助系统判断哪些事务可能产生锁冲突。
  • 优化锁定检查:在判断是否可以安全为一个事务分配锁时,可以快速检查表级意向锁,而无需扫描所有行锁。

为什么意向锁可以有以上好处?

加锁、释放锁方式

Innodb自动加上。 加锁需要满足:

  • 获取表中行的共享锁之前,必须先获得表上的IS锁,或是更强的锁。
  • 获取表中行的排他锁之前,必须先获取表中的IX锁。

AUTO-INC锁

概念 特殊的表级锁,在表中有自增列时,如果向该表插入数据,Innodb会首先获取这张表的AUTO-INC锁,等待插入完成后释放。用于保护自增列的生成值,保证唯一性和防止冲突。 可以使用Innodb_autoinc_lock_mode变量来配置自增锁的算法。

value

含义

0

传统锁模式,使用AUTO-INC锁

1

连续锁模式,采用轻量级锁

2

交错锁模式(MySQL8默认),AUTO-INC和轻量级锁之间灵活切换

如果在insert执行期间都持有锁,会降低并发度,这是传统锁模式。 可以在生成id后,但是insert未执行完成前,立刻释放锁,使得其他事务可以去生成新的自增id,这是轻量级锁。但是如果批量插入时,可能会导致批量插入的数据id并不自增。 在单行insert语句使用轻量级锁,在批量插入语句使用AUTO-INC锁,这是交错锁模式。

行锁

MySQL行锁在引擎层实现,不是所有引擎都支持行锁,Innodb指出,MyISAM不支持。 行锁主要有四类:

  • Record lock
  • Gap lock
  • Next-key lock
  • 插入意向锁

Record lock

概念 记录锁,针对索引记录的锁,锁定索引记录。

锁定范围 这要分情况讨论: 以以下语句为例:

SELECT * FROM user WHERE username='qxy' FOR UPDATE;
  • 如果有索引:
  • 且该索引唯一,那么直接在该索引上Record lock。
  • 如果索引不唯一,可能会增加间隙锁,确保事务处理期间,不会插入新的username='qxy'
  • 如果没有索引:
  • 升级锁的粒度,如表锁

Gap Lock

概念 间隙锁,锁定两个索引记录之间的间隙上,但是不包含该范围内的实际记录。

适用场景 用于防止其他事务插入记录到被锁定的范围内。

加锁、释放锁方式 Innodb隐式添加。

锁定范围 分情况讨论: 对于以下语句:

SELECT * FROM employees WHERE salary BETWEEN 50000 AND 100000 FOR UPDATE;
  • 有索引
  • 锁定索引记录(Record Lock),锁定索引之间的间隙
  • 无索引
  • 锁定全表扫描过程中的每一个间隙,锁定符合条件的记录。(可能会升级为表锁)

对于特殊的场景

-- id主键自增,且最大为40
SELECT * FROM employees WHERE id > 50 FOR UPDATE;

不会上Record Lock,会对>50的范围上锁,阻止其他事务在这个范围内插入。

Next-key Lock

概念 临键锁,Record Lock + Gap Lock,用于锁定一个范围和记录本身,左开右闭。

锁定范围 以以下为例:

-- id有索引
SELECT * FROM table_name WHERE id = 5 FOR UPDATE;

会锁定id=5记录本身,和id<5的间隙,如果id<5的最大记录是4,则会锁定(4,5),最终的效果是锁定(4,5]

乐观锁&悲观锁

概念上的抽象,与java中思想一致。 乐观锁:认为不会出现竞争,不上锁,但是通过CAS+版本号等对数据进行确认。 悲观锁:认为一定会发生竞争,对数据修改前,上排它锁。

死锁的发生和处理

死锁发生的场景 典型的是:AB商品上单修改库存的场景,且两个商品有优惠折扣绑定,需要在同时发生或者均失败,如果userA的顺序是先扣除A的库存,后扣除B的库存,userB则相反。在并发执行下可能导致死锁。 如果是在外贸检索平台的场景下,可能有多个程序需要去对商家的信息进行批量更新,考虑两个服务:1. 批量将商家升级为高级用户 2. 批量降低一些降低一些商家的活跃度指数。 将场景简化为就两个商家。

-- 服务1
start transaction;
update suppliers set grade = "Premium" where sid = 1;-- 执行顺序 1
update suppliers set grade = "Premium" where sid = 2;-- 执行顺序 3
commit;

-- 服务2
start transaction;
update suppliers set active_level = 2 where sid = 2;-- 执行顺序 2
update suppliers set active_level = 2 where sid = 1;-- 执行顺序 4
commit;

如果交替请求资源,发生持有并请求,则会发生死锁。

解决方法 以下是一些依赖数据库管理系统自动检测和解决死锁的方法:

  1. 设置超时时间innodb_lock_wait_timeout,默认是50s,当超时后,陷入死锁的事务回滚。
SET SESSION innodb_lock_wait_timeout = 10;
  1. 发起死锁检测,主动回滚死锁链表中的一个事务,让其它事务可以继续执行,需要将innodb_deadlock_detect设置为on。

参考

  1. https://zhuanlan.zhihu.com/p/570570330
  2. https://www.zhihu.com/search?type=content&q=数据库死锁
  3. https://www.bilibili.com/video/BV1ah4y1d7jN/?spm_id_from=333.337.search-card.all.click&vd_source=0de7a910c6389bd641e5d95811407d7d
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
655OvFktQQaY