Mysql之锁机制
全局锁
全局锁就是对整个数据库实例加锁。MySQL 提供了一个加全局读锁的方法FTWRL
Flush tables with read lock
全局锁的典型使用场景是,做全库逻辑备份,也就是把整库每个表都 select 出来存成文本。在备份过程中整个库完全处于只读状态,存在以下问题:
- 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆
- 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog,会导致主从延迟
可使用官方自带的逻辑备份工具mysqldump,配合参数–single-transaction,导数据之前启动一个事务,来确保拿到一致性视图。由于 MVCC 的支持,这个过程中数据是可以正常更新的。但需要注意的是:single-transaction 方法只适用于所有的表使用事务引擎的库(InnoDB )
表级锁
表锁
-- 给指定表加上表级读锁或写锁
lock tables … read/write -- 查看表锁定情况
-- In_use:表上锁及请求锁的数量(表锁时其他会话写请求堵塞)
-- Name_locked:表名是否被锁定,用于删除表和表重命名
show open tables where in_use >=1;
| Database | Table | In_use | Name_locked |
+----------+-------+--------+-------------+
| test | t | 1 | 0 | -- 释放被当前会话持有的任何锁
unlock tables
元数据锁
MDL(metadata lock),在 MySQL 5.5 版本中引入了 MDL,当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。
上图中如果session A事务未及时提交,就会一直占用MDL锁,session C中MDL写锁堵塞,后续的读请求因为MDL读锁堵塞,造成整个表不可读写。如果刚好是一张热点表,就有可能造成数据库线程爆满,从而整个库不可用。因此,对于长事务或者热点表的结构调整要慎重。
意向锁
意向锁是一种不与行级锁冲突的表级锁,分为两种:
意向共享锁(intention shared lock, IS):事务有意向对表中的某些行加共享锁(S锁)
-- 事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
SELECT column FROM table ... LOCK IN SHARE MODE;
意向排他锁(intention exclusive lock, IX):事务有意向对表中的某些行加排他锁(X锁)
-- 事务要获取某些行的 X 锁,必须先获得表的 IX 锁。
SELECT column FROM table ... FOR UPDATE;
意向锁是由数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在数据表对应意向锁。
其存在的意义在于:对同一张表加表锁时,只需要检测是否存在意向排他锁即可,不用检测表中行上的排他锁存在。
意向共享锁(IS) 意向排他锁(IX) 共享锁(S) 兼容 互斥 排他锁(X) 互斥 互斥 注意:这里的排他 / 共享锁指的都是表锁!!!意向锁不会与行级的共享 / 排他锁互斥!!!意向锁之间是互相兼容的!!!
行锁
行锁又称记录锁,记为LOCK_REC_NOT_GAP
-- 加共享锁(Shared Locks:S锁)
select…lock in share mode
-- 加排他锁(Exclusive Locks:X锁)
select…for update
两阶段锁协议
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。因此,如果事务中涉及多个行锁,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
在读已提交隔离级别下有一个优化,即:语句执行过程中加上的行锁,在语句执行完成后,就要把“不满足条件的行”上的行锁直接释放了,不需要等到事务提交。也就是说,读提交隔离级别下,锁的范围更小,锁的时间更短,这也是不少业务都默认使用读已提交隔离级别的原因。
死锁检测
- 直接进入等待,直到超时,可以通过参数 innodb_lock_wait_timeout 来设置,默认50s
- 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑
由于第一种策略,时间无法预知,太短可能误伤。正常情况下采用第二种策略,即主动死锁检测,但又会消耗CPU资源。对于热点行的更新可能导致性能问题,解决思路:
- 对于不会出现死锁的业务,可以关掉死锁检测,存在风险
- 控制并发度
- 客户端并发控制,但需要考虑分布式问题
- 数据库端并发控制:数据库中间件实现或者修改Mysql源码(大神玩家)
- 业务设计上拆分,单行数据拆分为多行,减小并发度,例如:1一个账户拆分为多个子账户
思考题
如果删除一个表里面的前 10000 行数据,有以下三种方法可以做到:
- 第一种,直接执行 delete from T limit 10000;
- 第二种,在一个连接中循环执行 20 次 delete from T limit 500;
- 第三种,在 20 个连接中同时执行 delete from T limit 500。
哪一种方法更好?为什么?
长事务、锁冲突
next-key lock
next-key lock由间隙锁(Gap Lock)和行锁组成,每个 next-key lock 是前开后闭区间,解决了幻读的问题。锁类型记为:LOCK_ORDINARY
间隙锁之间不存在冲突关系,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。
举例说明
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
session A | session B |
---|---|
begin; | |
select * from t where id=9 for update; | |
begin; | |
select * from t where id=9 for update; | |
insert into t values(9,9,9); | |
insert into t values(9,9,9); |
上述语句执行结果如何?出现了死锁,为什么呢?
session A、session B中select for update
由于id=9不存在,均加上了(5,10)的间隙锁,这也证明了间隙锁之间不存在冲突。接下来A、B都向这个间隙里插入数据,互相和对方持有的间隙锁冲突,相互等待形成死锁。如果开启了死锁检测,InnoDB会马上发现死锁关系,让A中插入报错返回。
从以上例子也可以看出,由于间隙锁的引入,虽然解决了幻读,可也影响了数据库的并发度。如果实际业务场景不需要保证可重复读,就可以考虑使用读已提交,同时binlog_format=row,保证主从同步的一致性。
加锁规则:两个原则、两个优化、一个bug
- 原则 1:加锁的基本单位是 next-key lock,前开后闭区间
- 原则 2:查找过程中访问到的对象才会加锁
- 优化 1:索引上的等值查询,给唯一索引加锁的时候,匹配上数据,next-key lock 退化为行锁
- 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁
- 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止
以上规则,其实可以理解为数据查找过程中,扫描到的对象应该加锁,排除逻辑上明显不需要加锁的对象,即为加锁范围
重点:
- 加锁是分步进行的,例如:
c>=10 and c<=11
,分解为c=10、c>10 and c<11、c=11依次进行锁申请 - 间隙由右边的间隙记录,这也导致了不同方向上扫描加锁范围不一样
- 从扫描到的第一个记录上作为起点,例如:普通索引c取值为[0,5,10,15,20],c>10和c>=10其分别第一个扫描到的数为15、10,因此第一个间隙锁为(10,15]、(5,10]
读已提交下的应用
在外键场景下有间隙锁,场景待确认
insert intention lock
插入意向锁,仅用于insert语句,表明将在某间隙插入记录,与间隙锁互斥关系如下:
X,GAP | S,GAP | intention-insert | |
---|---|---|---|
X,GAP | 兼容 | 兼容 | 互斥 |
S,GAP | 兼容 | 兼容 | 互斥 |
intention-insert | 兼容 | 兼容 | 唯一键冲突可能互斥 |
间隙锁之间不存在互斥关系(X、S表示是什么语句导致的间隙锁)
间隙锁可以堵塞区间内的插入意向锁,但插入意向锁不会堵塞后续的间隙锁
唯一键冲突,如果是主键加记录锁,如果是唯一索引加next-key lock
插入意向锁实验验证
mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
mysql> insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
-- 开启事务A
mysql> begin;
/**
在事务A中执行修改语句,id=7不存在,添加(5,10)的间隙锁,LOCK_MODE=X,GAP LOCK_DATA=10可以验证两个观点:
1、间隙锁是加在右边间隙上的
2、此处X并不代表10上加行锁,仅代表什么语句造成的,若改为select * from t where id=7 lock in share mode,LOCK_MODE就变为S,GAP
**/
mysql> update t set d=d+1 where id=7;
-- 在事务B中插入id=6的数据,需要申请插入意向锁,进入堵塞状态
mysql> insert into t values(6,6,6);
/**
事务A中已经添加了间隙锁,相同间隙的插入意向锁堵塞,LOCK_MODE=X,GAP,INSERT_INTENTION,LOCK_STATUS=WAITING
v8.0.11时,LOCK_MODE=X,GAP,INSERT_INTENTION标识是高版本新加的(此处使用的是8.0.21),插入意向锁是一种特殊的间隙锁
**/
mysql> select THREAD_ID,OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA
from performance_schema.data_locks;
+-----------+---------------+-------------+------------+-----------+------------------------+-------------+-----------+
| THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |
+-----------+---------------+-------------+------------+-----------+------------------------+-------------+-----------+
| 54 | demo | t | NULL | TABLE | IX | GRANTED | NULL |
| 54 | demo | t | PRIMARY | RECORD | X,GAP | GRANTED | 10 |
| 53 | demo | t | NULL | TABLE | IX | GRANTED | NULL |
| 53 | demo | t | PRIMARY | RECORD | X,GAP,INSERT_INTENTION | WAITING | 10 |
+-----------+---------------+-------------+------------+-----------+------------------------+-------------+-----------+
-- 开启事务C
mysql> begin;
-- 在事务C中插入id=16的数据,由于该间隙上没有间隙锁,申请插入意向锁成功
mysql> insert into t values(16,16,16);
/**
查询当前加锁情况,并没有发现插入意向锁,为什么?
插入意向锁是为了配合间隙锁解决幻读问题,在有间隙锁的情况下进行堵塞。此时没有间隙锁,不需要堵塞,所以就不用加插入意向锁吗?
但其他事务中相同行插入会产生冲突,说明这里还是有其他约束的,只是不用堵塞的插入意向锁转换成另外一种约束了
**/
mysql> select THREAD_ID,OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA
from performance_schema.data_locks;
+-----------+---------------+-------------+------------+-----------+-----------+-------------+-----------+
| THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |
+-----------+---------------+-------------+------------+-----------+-----------+-------------+-----------+
| 53 | demo | t | NULL | TABLE | IX | GRANTED | NULL |
+-----------+---------------+-------------+------------+-----------+-----------+-------------+-----------+
/**
证明其他约束的存在,新启一个事务,同样执行insert into t values(16,16,16),可以看到申请S锁堵塞,正常上一个事务中的插入有其他约束
这里需要进行唯一约束验证,获取id=16的读锁
**/
mysql> select THREAD_ID,OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA
from performance_schema.data_locks;
+-----------+---------------+-------------+------------+-----------+---------------+-------------+-----------+
| THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |
+-----------+---------------+-------------+------------+-----------+---------------+-------------+-----------+
| 53 | demo | t | NULL | TABLE | IX | GRANTED | NULL |
| 56 | demo | t | NULL | TABLE | IX | GRANTED | NULL |
| 56 | demo | t | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 16 |
| 56 | demo | t | PRIMARY | RECORD | S,REC_NOT_GAP | WAITING | 16 |
+-----------+---------------+-------------+------------+-----------+---------------+-------------+-----------+
-- 开启事务D
mysql> begin;
-- 在事务D中插入id=10,
mysql> insert into t values(10,10,10);
1062 - Duplicate entry '10' for key 'PRIMARY'
-- 在事务E中插入id=9
mysql> insert into t values(9,9,9);
(blocked)
/**V8.0.11
查看当前加锁情况,事务D插入语句检测到唯一冲突后在id=10上加了一个S锁
事务E中插入id=9,等待插入意向锁,没有间隙锁冲突,为什么会堵塞呢?
唯一键冲突加的应该不是一个记录S锁,应该是一个next-key lock (5,10],因为已经存在间隙锁,所以插入意向锁才会堵塞
这是MySQL的一个bug,在V8.0.16已经修复,事务E中插入不会堵塞(主键唯一冲突就是一个单纯的记录锁)
https://bugs.mysql.com/bug.php?id=93806
**/
mysql> select THREAD_ID,OBJECT_SCHEMA,OBJECT_NAME,INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA
from performance_schema.data_locks;
+--------+-----------+---------------+-------------+------------+-----------+-----------+-------------+-----------+
| ENGINE | THREAD_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |
+--------+-----------+---------------+-------------+------------+-----------+-----------+-------------+-----------+
| INNODB | 109 | demo | t | NULL | TABLE | IX | GRANTED | NULL |
| INNODB | 109 | demo | t | PRIMARY | RECORD | S | GRANTED | 10 |
| INNODB | 108 | demo | t | NULL | TABLE | IX | GRANTED | NULL |
| INNODB | 108 | demo | t | PRIMARY | RECORD | X,GAP | WAITING | 10 |
+--------+-----------+---------------+-------------+------------+-----------+-----------+-------------+-----------+
加锁检测
等MDL锁
-- 事务A
lock table test_data write; -- 由于事务A加了表锁,事务B堵塞
select * from test_data;
等flush
-- 关闭表t
flush tables t with read lock;
-- 关闭所有打开的表
flush tables with read lock; -- 事务A
select sleep(1) from t; -- 事务B:事务A中表t已打开,需要等待其结束
flush tables t; -- 事务C:等待事务B中flush结束
select * from t where id=1;
等行锁
-- 事务C
begin
update t set a=1 where id=1; -- 由于事务C行锁未提交,事务D相同行被堵塞
update t set a=1 where id=1;
锁及堵塞查询
-- 查看表阻塞的process id(MySQL启动时需要设置performance_schema=on,相比于设置为off会有10%左右的性能损失)
select blocking_pid from sys.schema_table_lock_waits; -- 查看行锁等待情况
select * from sys.innodb_lock_waits; -- MySQL5.7及之前查看事务锁情况
select * from performance_schema.innodb_locks;
-- MySQL8.0及之后查看事务锁情况
select * from performance_schema.data_locks;
-- 查看元数据加锁情况
select * from performance_schema.metadata_locks; -- 查看当前进程及状态
show processlist; -- 查看innodb引擎状态,可以获取一些关键信息点,例如:最近事务及加锁情况,对分析定位问题有帮助
show engine innodb status;
Mysql之锁机制的更多相关文章
- MySQL- 锁机制及MyISAM表锁
锁是计算机协调多个进程或线程并发访问某一资源的机制.在数据库中,除传统的计算资源(如CPU.RAM.I/O等)的争用以外,数据也是一种供许 多用户 共享的资源.如何保证数据并发访问的一致性.有效性是所 ...
- mysql insert锁机制【转】
最近再找一些MySQL锁表原因,整理出来一部分sql语句会锁表的,方便查阅,整理的不是很全,都是工作中碰到的,会持续更新 笔者能力有限,如果有不正确的,或者不到位的地方,还请大家指出来,方便你我,方便 ...
- 说一说MySQL的锁机制
锁概述 MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则. 最显著的特点是不同的存储引擎支持不同的锁机制,InnoDB支持行锁和表锁,MyISAM支持表锁. 表锁就是把 ...
- 关于MySQL的锁机制详解
锁概述 MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则. 最显著的特点是不同的存储引擎支持不同的锁机制,InnoDB支持行锁和表锁,MyISAM支持表锁. 表锁就是把 ...
- MySQL InnoDB锁机制
概述: 锁机制在程序中是最常用的机制之一,当一个程序需要多线程并行访问同一资源时,为了避免一致性问题,通常采用锁机制来处理.在数据库的操作中也有相同的问题,当两个线程同时对一条数据进行操作,为了保证数 ...
- mysql的锁机制详解
这段时间一直在学习mysql数据库.项目组一直用的是oracle,所以对mysql的了解也不深.本文主要是对mysql锁的总结. Mysql的锁主要分为3大类: 表级锁:存储引擎为Myisam.锁住整 ...
- mysql的锁机制,以及乐观锁,悲观锁,以及热点账户余额问题
mysql的简单锁机制. myisam 1.只支持表级锁,所以经常更新的表结构不适宜用. 2.select也会产生锁表 innodb 1.支持事务,行级锁,表级锁,执行行级锁的前提是sql语句的索引有 ...
- MySQL:锁机制和隔离事务级别
在mysql中的锁看起来是很复杂的,因为有一大堆的东西和名词:排它锁,共享锁,表锁,页锁,间隙锁,意向排它锁,意向共享锁,行锁,读锁,写锁,乐观锁,悲观锁,死锁.这些名词有的博客又直接写锁的英文的简写 ...
- MySQL 高级—— 锁机制
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.锁的概述 1.锁的定义 锁是计算机协调多个进程或线程并发访问某一资源的机制. 在数据库中,除传统的计 ...
- 一文详解MySQL的锁机制
一.表级锁.行级锁.页级锁 数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则. MySQL数据库由于其自身架构的特点,存在多种数据存储引擎, ...
随机推荐
- React PureComponent All In One
React PureComponent All In One import React, { // useState, // useEffect, // Component, PureComponen ...
- js & object & prototype & __proto__ & prototype chain
js & object & prototype & proto & prototype chain constructor prototype === instance ...
- 2020 front-end interview
2020 front-end interview https://juejin.im/post/5e083e17f265da33997a4561 xgqfrms 2012-2020 www.cnblo ...
- JavaScript Semicolon Insertion
JavaScript Semicolon Insertion https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-rega ...
- c++ string与wstring转换
wchar_t to char #include <comdef.h> const wchar_t* exepath = L"d:\\中文 路径\\中文 路径.exe" ...
- 伦尼斯酒庄(Chateau Renice)再次赞助亚洲50大餐厅赛事
连续几年来,伦尼斯酒庄(Chateau Renice)一直是亚洲50大最佳餐厅评选赛(Asia's 50 Best Restaurant Awards)的赞助商.2020年伦尼斯酒庄酒庄(Chatea ...
- 揭秘高倍矿币 Baccarat BGV,为何NGK DeFi的财富效应如此神奇?
作为区块链4.0代表的NGK公链,这次也将借助它自己的DeFi版块NGK Baccarat,开启属于它自己的千倍财富之旅. 如果说,比特币能让没有银行账户的人,可以在全球任何时间.地点都能自由进行交易 ...
- 1090 Highest Price in Supply Chain——PAT甲级真题
1090 Highest Price in Supply Chain A supply chain is a network of retailers(零售商), distributors(经销商), ...
- list 打乱排序
public IList<T> RandomSortList<T>(List<T> ListT) { Random random = new Random(); L ...
- SpringBoot接收map类型的参数
如果某个controller的某个接口的参数特别多,也可以使用map的方式来接收参数,接收之后使用get方法获取即可. 1)get请求方式,定义map接收方式 @RequestParam(requir ...