【MySQL】MySQL中的锁
全局锁
全局锁是对整个数据库实例加锁,整个库处于只读状态。
flush tables with read lock
适用场景
全局锁适用于做全库逻辑备份,但是整个库处于只读状态,在备份期间,所有的更新操作、DDL将会被阻塞,会对业务产生影响。
single-transaction
mysqldump备份时可以使用–single-transaction参数,在备份数据之前启动一个事务,借助于MVCC获取到一致性视图,保证在备份的过程中,还支持数据的更新操作。
但是single-transaction只能用于支持事务的引擎,比如MyISAM不支持事务,所以使用MyISAM引擎的时候,是无法使用single-transaction的。
表级锁
表级锁分为表锁和元数据锁。
表锁
表锁从名字上就可以看出锁的是数据库表(Table),语法为:
# 锁住某张表
lock tables 表名 read/write
# 释放锁
unlock tables 表名
因为表锁的粒度太大,将整张表锁住,所以一般不使用表锁。
元数据锁MDL
元数据锁(meta data lock)不需要显示的使用,访问表的时候会自动添加MDL锁,添加MDL锁的原因是防止表结构出现不一致,假设查询数据的过程中,突然表结构被修改了,与最开始拿到的表结构不一致,在某些场景下可能会影响非常大。
MDL读锁
在对表做增删改查的时候,添加的是MDL读锁。
为什么添加的是读锁?
因为读锁之间不互斥,可以保证多个线程同时对一张表进行增删改查。
MDL写锁
在对表结构做修改的时候,添加的是MDL写锁。
为什么是写锁?
因为写锁与读锁之间相互互斥,当然写锁和写锁之间更是互斥的,既然要保证数据修改的安全性,那么如果有读操作在进行,是不能进行表结构变更操作的,反之亦是如此,如果正在修改表结构,也是不能进行读操作的,必须要等待前一个操作完成才可以进行下一个操作。所以使用了写锁,通过互斥保证数据操作的安全性。
需要注意的是在事务中添加MDL锁的时候,直到整个事务提交后才会释放锁,如果此时遇到长事务,就会一直占用锁。如果在这种情况下需要修改表结构,可以通过以下两种方式:
通过innodb_trx查询事务的trx_mysql_thread_id,将事务kill掉:
mysql> SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id,trx_autocommit_non_locking FROM information_schema.innodb_trx;
+-----------------+-----------+---------------------+---------------------+----------------------------+
| trx_id | trx_state | trx_started | trx_mysql_thread_id | trx_autocommit_non_locking |
+-----------------+-----------+---------------------+---------------------+----------------------------+
| 422151119956664 | RUNNING | 2021-07-02 23:27:06 | 5 | 0 |
+-----------------+-----------+---------------------+---------------------+----------------------------+
1 row in set (0.00 sec)
kill事务命令:kill 事务线程ID(trx_mysql_thread_id)
mysql> kill 5;
Query OK, 0 rows affected (0.00 sec)
如果请求很频繁,可能刚kill掉就有新的事务到来,这个时候可以在修改表结构的时指定等待时间来获取MDL锁,如果在等待时间内都没有拿到锁,就先放弃,之后在合适的时间再修改表结构。
行锁
行锁锁住的是数据库表的行记录,但不是所有的引擎都支持行锁,比如MyISAM就不支持,所以对于MyISAM只能使用表锁。
如果某个字段存在索引,那么以该字段为查询条件时添加的行锁只需要锁住满足条件的数据行即可,如果不存在索引,MySQL需要全表扫描查找数据,此时会锁住所有的行,也就是退化为了表锁。
两阶段锁协议
在InnoDB事务中,行锁在需要的时候才加上,比如开始执行一个UPDATE语句,但是并不是UPDATE语句结束之后锁就释放了,而是在事务结束之后才释放,所以在实际开发中,可以将容易引起锁冲突的操作尽量往后放,减少锁的时间。
间隙锁Gap Lock
MySQL默认的隔离级别为可重复读,以下情况没有特殊说明,默认都是在可重复读隔离级别下。
当前读和快照读
在看间隙锁之前先看下MySQL的当前读和快照读。
快照读
MySQL的MVCC机制,在每个事务开启时会为其生成一个一致性视图,以此实现读提交/可重复读隔离级别,快照读指的就是从这个生成的一致性视图读取数据。
关于MVCC机制可参考:【MySQL】MVVC机制
当前读
当前读指的是读取undo log版本链中最新的记录,也就是读取最新的数据(已经提交的),如果是更新(update/insert/delete)操作都是当前读,select在可重复读的隔离级别下是快照读,不过可以使用以下语句使其变成当前读:
select xx from xx lock in share;
select xx from xx for update;
lock in share mode会加读锁,for update会加写锁,这两种语句都会进行当前读。
间隙锁
行锁是在数据表行记录上添加的锁,并不能锁住间隙,如果有INSERT操作,一样可以执行成功,此时就出现了幻读问题,为了解决幻读的问题,引入了间隙锁Gap Lock。间隙锁,就是在行与行之间的间隙处也增加了锁,它锁住的是一个范围区间,范围左右都是开区间。
来看一个例子,现有一张user表,分别有id(主键索引)、name、age三个字段,有以下1条数据:
| id | name | age |
|---|---|---|
| 1 | a | 15 |
假设没有间隙锁,加锁时只针对记录加行锁,来看一个例子:
事务A在T1时刻查询age为15的数据,这里使用for update表示当前读,并且对这条记录加锁,此时可以查到一条记录;
T2时刻,事务B又新增了一条age为15的数据,并进行了提交;
T3时刻事务A中再查询时,使用了当前读,会发现可以查到两条记录,多出了一条age为15(事务B提交的那条)的数据,与T1时刻的数据不一致,此时就产生了幻读;

为什么使用for update进行当前读?
因为MySQL默认隔离级别是可重复读,如果不使用for update进行当前读,事务开启时创建一致性视图,使用的是快照读,所以读不到本事务开启后其他事务所做的操作,不会出现幻读。
而for update每次都要读取最新的数据,所以会出现幻读问题。为什么会出现幻读?
for update已经加了写锁,按理说age为15的数据应该都会被锁住才对,为什么还可以新增一条age为15的数据?
因为行锁只能锁住某行数据,由于age字段上没有加索引,会锁住所有行,但是并没有锁住行之间的间隙,此时新增的那条数据还不存在,可以利用间隙这个漏洞新增一条age为15的数据。
为了解决幻读问题,引入了间隙锁,假设当前表中有三条数据,age分别为15、20、25:
| id | name | age |
|---|---|---|
| 1 | a | 15 |
| 2 | b | 20 |
| 3 | c | 25 |
此时会存在四个间隙:
(-∞,15)、(15,20)、(20,25)、(25,+∞)
如果此时在age上执行查询(for update当前读,会加写锁):
select * from test where age = 15 for update;
因为age列没有添加索引,mysql会锁住所有行以及行之间的间隙,同一个时刻另外一个事务再执行INSERT语句:
insert into user(id, name, age) values(2, b, 15);
由于所有的区间都加了锁,此时会被阻塞,这样就防止了幻读。
需要注意间隙锁在在可重复读隔离级别下才会生效。
临键锁next-key lock
行数锁住的是某行记录,间隙锁锁的是行之间的间隙(左右都是开区间),将行数和间隙锁结合起来就是临键锁next-key lock,每个next-key lock都是左开右闭区间,以上面为例,next-key lock的所有区间为:
(-∞,15]、(15,20]、(20,25]、(25,+supremum]
InnoDB会为每个索引增加一个不存在的最大值supremum。
在可重复读隔离级别下,MySQL的加锁基本单位是临键锁,不过有两个优化:
- 索引上进行等值查询,如果是唯一索引,临键锁将会退化为行锁;
- 索引上进行等值查询,向右遍历时且最后一个值不满足等值条件时,临键锁退化为间隙锁;
第一个优化比较好理解,因为是唯一索引,为了提高性能,可以退化为行锁,只需要对那条数据加锁即可。
来看第二个优化,还是以上面的user表为例,有id(主键索引)、name(未添加索引)、age(添加了索引,注意与上面的例子区别,这里加了索引)三个字段,此时有以下数据:
| id | name | age |
|---|---|---|
| 0 | aaa | 0 |
| 1 | a | 15 |
| 2 | b | 20 |
此时next-key lock区间为:
(0,15]、(15,20]、(20,+supremum]

T1时刻使用lock in share mode从user表中查询age是15的id;
由于默认加锁单位是临键锁,此时会给(0,15]这个区间加临键锁,由于age列是普通索引,需要继续向右遍历区间,查到age为20停止,访问到的对象都要加锁,
所以本应该对(15,20]这个区间也加锁,但是根据优化2,这个区间的最后一个值20不满足age=15这个等值条件,所以退化为间隙锁(15,20)。T2时刻,向user表插入age为17的数据,由于对(0,15]和(15,20)这两个区间加锁,所以session B会进行阻塞;
MySQL在可重复读隔离级别下是否能解决幻读问题?
答案是不能,MVCC机制可以实现读提交/可重复读两个级别,在可重复读隔离级别下,如果使用快照读,确实可以避免出现幻读的问题:

事务A在T1时刻查询age为15的数据,注意这里是普通的select语句是快照读,此时可以查到一条记录;
T2时刻,事务B又新增了一条age为15的数据,并进行了提交;
T3时刻事务A中再查询时,同样使用快照读,由于是从一致性视图中读取,并不会读到事务B提交的数据;
在可重复读隔离级别下,如果使用当前读,由于临键锁和间隙锁的存在,也可以避免幻读,上面的临键锁和间隙锁的例子都可以说明。
不过这并不等价于在可重复读隔离级别下就可以避免幻读的问题,来看一个例子:

事务A在T1时刻查询age为15的数据,注意这里是普通的select语句是快照读,此时可以查到一条记录;
T2时刻,事务B又新增了一条age为15的数据,并进行了提交;
T3时刻事务A中再查询时,使用for update进行当前读,此时依旧可以查询到事务B提交的数据,所以就出现了幻读;
所以如果有快照读又有当前读的情况下,并不能解决幻读问题。
死锁
在使用锁的过程中,如果不同线程之间出现循环依赖资源,都在互相等待对方释放锁,就有可能造成死锁。

同样以上面的user表为例:
- T1时刻事务A更新id为1的数据,会对id为1的行加锁;
- T2时刻事务B更新id为2的数据,会对id为2的行加锁;
- T3时刻事务A同样去更新id为2的数据,此时已被事务B加锁,只能等待;
- T4时刻事务B需要更新id为1的数据,而这行数据已经被事务A加锁,事务B只能等待;
- 事务A和事务B互相等待对方占有的锁,形成循环,造成死锁;
对于死锁的解决方案有以下两种:
(1)通过innodb_lock_wait_timeout指定超时时间,默认值是50s,如果在某个时间内还没有获取到锁就超时放弃。
(2)将innodb_deadlock_detect设置为on开启死锁检测,每个事务到来被锁阻塞的时候,都会检测是否有可能导致死锁,当然开启死锁检测是有性能消耗的,高并发情况下需要消耗大量的CPU资源。
【MySQL】MySQL中的锁的更多相关文章
- MySql InnoDB中的锁研究
# MySql InnoDB中的锁研究 ## 1.InnoDB中有哪些锁### 1. 共享和排他(独占)锁(Shared and Exclusive Locks) InnoDB实现标准的行级锁定,其中 ...
- MySQL/InnoDB中,对于锁的认识
MySQL/InnoDB的加锁,一直是一个面试中常问的话题.例如,数据库如果有高并发请求,如何保证数据完整性?产生死锁问题如何排查并解决?我在工作过程中,也会经常用到,乐观锁,排它锁,等.于是今天就对 ...
- 聊一聊 MySQL 数据库中的那些锁
在软件开发中,程序在高并发的情况下,为了保证一致性或者说安全性,我们通常都会通过加锁的方式来解决,在 MySQL 数据库中同样有这样的问题,一方面为了最大程度的利用数据库的并发访问,另一方面又需要保证 ...
- mysql事务原理以及锁
一.Innodb事务原理 1.什么是事务 a.事务(Transaction)是数据库区别于文件系统的重要特性之一,事务会把数据库从一种一致性状态转换为另一种一致性状态. b.在数据库提交时,可以确保要 ...
- MySQL中的锁(表锁、行锁)
锁是计算机协调多个进程或纯线程并发访问某一资源的机制.在数据库中,除传统的计算资源(CPU.RAM.I/O)的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性.有效性是所在有数 ...
- MySQL数据库InnoDB存储引擎中的锁机制
MySQL数据库InnoDB存储引擎中的锁机制 http://www.uml.org.cn/sjjm/201205302.asp 00 – 基本概念 当并发事务同时访问一个资源的时候,有可能 ...
- MySQL系列(五)---总结MySQL中的锁
MySQL中的锁 目录 MySQL系列(一):基础知识大总结 MySQL系列(二):MySQL事务 MySQL系列(三):索引 MySQL系列(四):引擎 概述 MyISAM支持表锁,InnoDB支持 ...
- MySQL中InnoDB锁不住表的原因
MySQL中InnoDB锁不住表是因为如下两个参数的设置: mysql> show variables like '%timeout%'; +-------------------------- ...
- Mysql中的锁机制
原文:http://blog.csdn.net/soonfly/article/details/70238902 锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的 计算资源(如 ...
- [转]MySQL中乐观锁、悲观锁(共享锁、排他锁)简介
InnoDB与MyISAM Mysql 在5.5之前默认使用 MyISAM 存储引擎,之后使用 InnoDB. MyISAM 操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高 ...
随机推荐
- 关于开设go语言专题的说明
这个博客账号已经停更了约5年,期间我经历了比较多的事情,开始几个github开源项目,例如go-etl已经有两年.现在已经我在这方面有了许多积累,足够开设相关的专题.不用担心博主会断更,因为相关的内容 ...
- Flutter系列文章-Flutter进阶2
这一节我将再详细地为您介绍 Flutter 进阶主题,包括导航和路由.状态管理.异步处理.HTTP请求和Rest API,以及数据持久化.让我们逐个介绍这些主题. 1.导航和路由 在 Flutter ...
- go项目实现在配置文件实现配置项统一管理
转载请注明出处: go项目中实现配置项统一管理,实现逻辑:将 配置项整理为一个json的数据结构,并保存到go.conf文件中,然后在go项目启动main方法中加载 go.conf 文件,读取go.c ...
- AttributeError: 'EmailUse' object has no attribute 'SendMail'
错误原因:函数名与模块名重复 解决方案:不要将函数名与模块名重复
- 八 Appium常用方法介绍(转)
由于appium是扩展了Webdriver协议,所以可以使用webdriver提供的方法,比如在处理webview页面,完全可以使用webdriver中的方法.当然在原生应用中,也可以使用. 1.元素 ...
- [python]使用faker库生成测试数据
简介 Faker库可用于随机生成测试用的虚假数据. 可生成的数据参考底部的参考链接. 安装: python -m pip install faker 快速入门 from faker import Fa ...
- 三维模型OSGB格式轻量化技术在大规模场景的加载和渲染的作用分析
三维模型OSGB格式轻量化技术在大规模场景的加载和渲染的作用分析 在移动设备上,大规模场景的加载和渲染是一个不容忽视的问题.对于OSGB格式轻量化处理来说,大规模场景的加载和渲染也是其中一项重要的任务 ...
- 《深入理解Java虚拟机》读书笔记:基于栈的字节码解释执行引擎
虚拟机是如何调用方法的内容已经讲解完毕,从本节开始,我们来探讨虚拟机是如何执行方法中的字节码指令的.上文中提到过,许多Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行) ...
- 想让你的工作轻松高效吗?揭秘Java + React导出Excel/PDF的绝妙技巧!
前言 在B/S架构中,服务端导出是一种高效的方式.它将导出的逻辑放在服务端,前端仅需发起请求即可.通过在服务端完成导出后,前端再下载文件完成整个导出过程.服务端导出具有许多优点,如数据安全.适用于大规 ...
- Unity UGUI的ScrollRect(滚动视图)组件的介绍及使用
Unity UGUI的ScrollRect(滚动视图)组件的介绍及使用 1. 什么是ScrollRect组件? ScrollRect(滚动视图)是Unity UGUI中的一个常用组件,用于在UI界面中 ...