InnoDB学习(五)之数据库锁
InnoDB存储引擎的默认隔离级别事可重复读,MVCC多版本并发控制仅仅解决了快照读情况下的数据隔离,而对于当前读,InnoDB通过锁来进行并发控制。
InnoDB锁
本文主要参考了MySQL官方文档,并在上面添加了一些自己的理解,有兴趣看英文的也可以看MySQL官方文档。本文分为以下章节:
- 共享锁和独占锁;
- 意向锁;
- 行锁;
- 间隙锁;
- Next-Key锁
- 插入意向锁;
- 自增锁;
共享锁和排他锁
InnoDB锁的最小粒度是行锁,行锁可以分为两大类:共享锁(S)和独占锁(X)。
- 共享锁:持有某行数据共享锁的事务,可以读取行锁对应行的数据;
- 独占锁:持有某行数据独占锁的事务,可以修改行锁对应行的数据;
如果事务T1持有行R的共享锁,那么对于事务T2对行R的访问分为两种情况:
- 如果事务T2请求行R的共享锁,则事务T2可以请求成功,请求完成后事务T1和事务T2同时持有行R的共享锁;
- 如果事务T2请求行R的排他锁,事务T2会被阻塞,直到事务T1释放锁或者事务超时回滚;
如果事务T1持有行R的共享锁,那么不管事务T2请求R行的共享锁还是排他锁,都会被事务T1阻塞,直到事务T1释放锁或事务T2回滚。
意向锁
InnoDB支持支持多种粒度的锁,比如对于以下两个SQL语句,加锁的对象就完全不同:
SELECT * FROM USER_INFO WHERE ID = 1 FOR UPDATE,其中ID是主键,ID=1的数据行存在,那么这句SQL会获取ID=1的数据行的独占锁;LOCK TABLES USER_INFO WRITE,其中USER_INFO表存在,那么这句SQL会获取USER_INFO表的独占锁;
表锁和行锁之间也存在互斥的情况,比如表上的独占锁和表中每一行数据的独占锁之间冲突(锁表了当然不允许修改表中的内容),这种互斥要怎么实现呢?InnoDB使用了意向锁实现表锁和行锁之间的互斥,意向锁是表级别的锁,对一行数据添加独占锁或排他锁时,会先向数据行所在的表添加意向锁,意向锁分为两种类型:
- 共享意向锁:事务会对表中的某一行数据添加共享锁;
- 排他意向锁:事务会对表中的某一行数据添加排他锁;
所以对表添加意向锁的情况也分两种:
- 如果事务需要获取某一行数据的共享锁,那么必然会首先获取数据所在表的共享意向锁,如SQL语句
SELECT * FROM USER_INFO WHERE ID = 1 LOCK IN SHARE MODE会首先向表USER_INFO添加共享意向锁; - 如果事务需要获取某一行数据的排他锁,那么必然会首先获取数据所在表的排他意向锁,如SQL语句
SELECT * FROM USER_INFO WHERE ID = 1 FOR UPDATE会首先向表USER_INFO添加共享排他锁;
表锁和意向锁之间的冲突情况如下所示:
| 表排他锁 | 共享排他锁 | 表共享锁 | 共享意向锁 | |
|---|---|---|---|---|
| 表排他锁 | 冲突 | 冲突 | 冲突 | 冲突 |
| 共享排他锁 | 冲突 | 不冲突 | 冲突 | 不冲突 |
| 表共享锁 | 冲突 | 冲突 | 不冲突 | 不冲突 |
| 共享意向锁 | 冲突 | 不冲突 | 不冲突 | 不冲突 |
如果事务请求的表锁和表现有的锁之间不冲突,那么事务可以请求锁成功;如果事务请求的锁和表现有的锁冲突,那么事务必须等待表锁被释放,或者当前事务需要回滚。
我们可以注意到,意向锁之间不会互斥,因为意向锁代表的是修改表中的某一行数据,两个意向锁表示修改表中的两行数据,所以两个意向锁不一定会冲突。意图锁只会和表锁之间冲突,如LOCK TABLES USER_INFO WRITE会向表添加表锁。
InnoDB中,我们可以通过SHOW ENGINE INNODB STATUS语句查看表锁状况,以下为锁状况示例:
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
行锁
行锁是添加在索引上的锁,例如对于SELECT * FROM USER_INFO WHERE ID = 1 FOR UPDATE,在ID是唯一索引的情况下,该SQL语句会对ID对应的索引节点上添加排他锁,阻止其它事务修改该行数据。行锁添加的对象是索引节点,如果表没有定义索引,InnoDB会创建一个隐藏的聚集索引,并使用该索引来添加行锁。
InnoDB中,我们可以通过SHOW ENGINE INNODB STATUS语句查看行锁状况,以下为锁状况示例:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
间隙锁
间隙锁加锁的对象是索引之间的间隙,例如如果对于SQL语句SELECT * FROM USER_INFO WHERE ID>10 and ID<20 FOR UPDATE,该事务会向数据库中ID索引树上10~20之间的所有节点间隙添加间隙锁。当另一个事务尝试向数据库中插入ID=15的记录时,会被间隙锁阻塞。
间隙锁的间隙中可以包含多个索引节点、单个索引阶段或者不包含任何节点。间隙锁主要用于解决可重复读隔离级别下的幻读问题。
对于唯一索引,如果使用等值查询,那么间隙锁会退化为行锁,如下SQL中,ID是唯一索引列,并且ID=100的数据存在,那么以下SQL只会添加行锁:
SELECT * FROM child WHERE id = 100;
如果ID不是唯一索引,那么上文中的SQL语句则会给ID索引树中的Id=100和前一个节点之间的间隙添加GAP锁,间隙锁之间不冲突,并且两个间隙锁之间的节点被删除之后,两个间隙锁还会合并为一个间隙锁。
InnoDB中的间隙锁只有一个目的,阻止向间隙内插入数据,间隙锁只和插入意向锁冲突,和其它任何锁都不冲突。可以通过将事务隔离级别更改为读已提交或启用innodb_locks_unsafe_for_binlog系统变量来禁用间隙锁。
在禁用间隙锁的情况下,InnoDB还会将释放不匹配行的记录锁(违反了加锁的2PL原则)。对于UPDATE语句,InnoDB执行"半一致"读取:读取最新提交的数据,MySQL使用最新提交的数据判断是否符合UPDATE语句中的WHERE条件。
Next-Key锁
Next-Key锁是行锁和间隙锁的组合,在InnoDB唯一索引加锁的过程中,InnoDB会从索引中查找符合条件的索引节点,并对这些符合条件的索引节点添加行锁。
如果对某行记录加Next-Key锁而不是行锁,那么而Next-Key锁不仅会对记录本身添加行锁,还会对行锁之前的间隙添加间隙锁,二者组合成了Next-Key。Next-Key不允许其它事务向加锁的间隙中插入数据。
假设ID索引包含值10、11、13和20,那么先索引的节点添加Next-Key锁可能会有以下几种情况,下文中圆括号表示排除间隙,方括号表示包含端点:
- 如果对索引10所在的节点加Next-Key,加锁范围为(负无穷, 10];
- 如果对索引11所在的节点加Next-Key,加锁范围为(10, 11];
- 如果对索引13所在的节点加Next-Key,加锁范围为(11, 13];
- 如果对索引20所在的节点加Next-Key,加锁范围为(13, 20];
- 如果20之后的间隙加Next-Key,加锁范围为(20, 正无穷);
对于最后一个间隙,可以理解为:InnoDB中有一个虚拟的最大节点,会在该节点上添加Next-Key.
InnoDB中,我们可以通过SHOW ENGINE INNODB STATUS语句查看Next-Key锁状况,以下为锁状况示例:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;
插入意向锁
插入意向锁是向数据库中插入一行新数据时,需要向插入间隙添加的一种间隙锁。插入意向锁之间不冲突,例如两个事务分别打算向(4,7]之间的间隙插入5和6,这两个事务都会向(4,7]中的间隙添加插入意向锁,但是二者互不阻塞。
假设有两个事务,事务A和事务B,数据库表中包含两条记录90和102。事务A对ID大于100的索引记录添加Next-Key独占锁定:
mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id |
+-----+
| 102 |
+-----+
事务B尝试向数据库中插入一条101的记录:
mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
通过SHOW ENGINE INNODB STATUS,我们可以看到此时数据库的锁等待情况:
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000066; asc f;;
1: len 6; hex 000000002215; asc " ;;
2: len 7; hex 9000000172011c; asc r ;;...
自增锁
自增锁是一种特殊的表级锁,当表中包含AUTO_INCREAMENT的表中的事务使用。在最简单的情况下,如果一个事务正在向表中插入数据行,该事务会占有自增所,其它任何事务在向表中插入数据时都会被该锁阻塞。我们可以通过innodb_autoinc_lock_mode变量控制自增锁的自增的算法,MySQL对自增锁有很多优化,本文不详细介绍。
我是御狐神,欢迎大家关注我的微信公众号:wzm2zsd

参考文档
本文最先发布至微信公众号,版权所有,禁止转载!
InnoDB学习(五)之数据库锁的更多相关文章
- mysql第五篇 : MySQL 之 视图、触发器、存储过程、函数、事物与数据库锁
第五篇 : MySQL 之 视图.触发器.存储过程.函数.事物与数据库锁 一.视图 视图是一个虚拟表(非真实存在的),其本质是‘根据SQL语句获取动态的数据集,并为其命名‘ ,用户使用时只需使用“名称 ...
- MySQL数据库InnoDB存储引擎中的锁机制
MySQL数据库InnoDB存储引擎中的锁机制 http://www.uml.org.cn/sjjm/201205302.asp 00 – 基本概念 当并发事务同时访问一个资源的时候,有可能 ...
- Oracle学习(十七)数据库锁在分布式系统里的应用(老板,乐观锁了解一下?~)
一.引言 随着业务量的增加,单机部署已经无法满足日常需求了,我们可能会把代码部署到多台服务器上去来进行服务的扩容,也就是负载均衡,那在这种场景下,怎么能实现锁的概念呢? 那么我们知道如果是一台主机部署 ...
- MySQL 之 视图、触发器、存储过程、函数、事物与数据库锁
浏览目录: 1.视图 2.触发器 3.存储过程 4.函数 5.事物 6.数据库锁 7.数据库备份 1.视图 视图:是一个虚拟表,其内容由查询定义.同真实的表一样,视图包含一系列带有名称的列和行数据 视 ...
- day41 python【事物 】【数据库锁】
MySQL[五] [事物 ][数据库锁] 1.数据库事物 1. 什么是事务 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性 ...
- 数据库MySQL之 视图、触发器、存储过程、函数、事务、数据库锁、数据库备份、事件
数据库MySQL之 视图.触发器.存储过程.函数.事务.数据库锁.数据库备份.事件 浏览目录 视图 触发器 存储过程 函数 事务 数据库锁 数据库备份 事件 一.视图 1.视图概念 视图是一个虚拟表, ...
- 数据库锁(mysql)
InnoDB支持表.行(默认)级锁,而MyISAM支持表级锁 本文着中介绍InnoDB对应的锁. mysql锁主要分为以下三类: 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高 ...
- 一例胜千言,详谈SQL Sever数据库锁
1 前言 数据库大并发操作要考虑死锁和锁的性能问题.看到网上大多语焉不详(尤其更新锁),所以这里做个简明解释,为下面描述方便,这里用T1代表一个数据库执行请求,T2代表另一个请求,也可以理解为T1为一 ...
- MongoDB索引(一) --- 入门篇:学习使用MongoDB数据库索引
这个系列文章会分为两篇来写: 第一篇:入门篇,学习使用MongoDB数据库索引 第二篇:进阶篇,研究数据库索引原理--B/B+树的基本原理 1. 准备工作 在学习使用MongoDB数据库索引之前,有一 ...
随机推荐
- Web Api 宿主的搭建
首先我们要清楚一个概念,宿主.宿主是什么意思?先从了解一下Hosting开始吧! 有关Hosting的基础知识 Hosting是一个非常重要,但又很难翻译成中文的概念.翻译成:寄宿,大概能勉强地传达它 ...
- 华为云企业级Redis评测第一期:稳定性与扩容表现
摘要:采用Redis Labs推出的多线程压测工具memtier_benchmark对比测试下GaussDB(for Redis) 和原生Redis的特性差异. 本文分享自华为云社区<华为云企业 ...
- 『MdOI R1』Treequery
我们可以思考怎么做呢. 首先我们需要进行一些分类讨论: 我们先思考一下如果所有关键点都在 \(p\) 的子树内, 那显然是所有关键点的 \(Lca\) 到 \(p\) 距离. 如果所有关键点一些在 \ ...
- CF1466G Song of the Sirens
题目传送门 题意简述:给出 \(n,s_0,t\ (n=|t|)\),定义 \(s_i=s_{i-1}+t_i+s_{i-1}\).多次询问给出 \(k,m\),求 \(m\) 在 \(s_k\) 中 ...
- 如何利用efetch从NCBI中批量下载数据?
目录 找序列 下序列 假设我要从NCBI中下载全部水稻的mRNA序列,如何实施? 找序列 第一步,肯定是找到相关序列. 我从ncbi taxonomy进入,搜索oryza.因为要搜索mRNA核酸序列, ...
- python20判断变量是否存在
python中检测某个变量是否有定义 第一种方法使用内置函数locals(): locals():获取已定义对象字典 'testvar' in locals().keys() 第二种方法使用内置函数d ...
- PHP识别二维码(php-zbarcode)
PHP识别二维码(php-zbarcode) 标签: php二维码扩展 2015-11-06 17:12 609人阅读 评论(0) 收藏 举报 分类: PHP(1) Linux 版权声明:本文为博 ...
- php代码审计入门前必看
首先先介绍什么是代码审计? 代码审计:是指针对源代码进行检查,寻找代码中的bug,这是一项需要多方面技能的技术 包括:对编程的掌握,漏洞形成原理的理解,系统和中间件等的熟悉 2.为什么要进行代码审计, ...
- 巩固javaweb第十天
巩固内容: HTML <meta> 元素 meta标签描述了一些基本的元数据. <meta> 标签提供了元数据.元数据也不显示在页面上,但会被浏览器解析. META 元素通常用 ...
- android studio 使用 aidl(三)权限验证
这篇文章是基于android studio 使用 aidl (一) 和 android studio 使用 aidl(二) 异步回调 下面的代码都是简化的,如果看不懂请先移步上2篇文章 网上的东西太坑 ...