简介
参考https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-gap-locks。
InnoDB引擎实现了标准的行级别锁(S和X)。InnoDB引擎加锁原则遵循二段锁协议,即事务分为两个阶段,事务开始后进入加锁阶段,事务commit或者rollback就进入解锁阶段。InnoDB引擎下锁的影响因素很多,隔离级别不同,是否使用索引等都会产生不同的锁结果。
 
查看锁和事务
当出现ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction,要解决是一件麻烦的事情。特别是当一个SQL执行完了,但未COMMIT,后面的SQL想要执行就是被锁,超时后结束,DBA光从数据库无法着手找出源头是哪个SQL锁住了。有时候看看 show engine innodb status, 并结合 show full processlist 能暂时解决问题,但一直不能精确定位。在5.5中,information_schema 库中增加了三个关于锁的表(MEMORY引擎)。
 
INNODB_LOCKS
提供有关InnoDB事务已请求但尚未获得的以及事务正在阻塞另一个事务的锁的信息。
| lock_id | InnoDB内部的唯一锁ID | 
| lock_trx_id | 拥有这个锁的事务ID | 
| lock_mode | 请求的锁,S, X, IS, IX等 | 
| lock_type | 锁类型,RECORD或者TABLE  | 
| lock_table | 被锁的表或包含被锁记录的表 | 
| lock_index | 被锁的索引,不是行级锁时为NULL | 
| lock_space | 被锁的表空间号,不是行级锁时为NULL | 
| lock_page | 被锁的页号,不是行级锁时为NULL | 
| lock_rec | 被锁的Heap号,不是行级锁时为NULL | 
| lock_data | 被锁的记录的主键,不是行级锁时为NULL | 
 
 
INNODB_LOCK_WAITS
当前等待的锁。
| requesting_trx_id | 正在请求的、受阻的事务ID | 
| requested_lock_id | 事务正在等待的锁ID | 
| blocking_trx_id | 阻塞其他事务的事务ID | 
| blocking_lock_id  | 阻塞其他事务的事务持有的锁ID | 
 
 
INNODB_TRX
当前事务。
| trx_id  | InnoDB内部的唯一事务ID | 
| trx_state | 事务状态,RUNNING, LOCK WAIT等 | 
| trx_started | 事务开始时间 | 
| trx_requested_lock_id | 事务正在等待的锁ID | 
| trx_wait_started | 事务开始等待的时间 | 
| trx_weight | 事务的权重,当发生死锁回滚的时候,优先选择该值最小的进行回滚 | 
| trx_mysql_thread_id | 事务线程ID,即show full processlist中的ID | 
| trx_query | 执行的SQL语句 | 
| trx_operation_state | 事务当前操作状态 | 
| trx_tables_in_use | 执行当前SQL时有多少个表被使用 | 
| trx_tables_locked | 执行当前SQL时有多少个表有行锁 | 
| trx_lock_structs | 事务保留的锁的数量 | 
| trx_lock_memory_bytes | 事务锁占据的内存大小(B) | 
| trx_rows_locked | 事务锁定的大概行数 | 
| trx_rows_modified | 事务修改和插入的行数 | 
| trx_concurrency_tickets | 即innodb_concurrency_tickets系统变量 | 
| trx_isolation_level | 事务隔离级别 | 
| trx_unique_checks | 是否唯一性检查 | 
| trx_foreign_key_checks | 是否外键检查 | 
| trx_last_foreign_key_error | 最后的外键错误详细信息 | 
| trx_adaptive_hash_latched |  | 
| trx_adaptive_hash_timeout |  | 
| trx_is_read_only | 1表示事务是只读的 | 
| trx_autocommit_non_locking | 1表示事务是不包含FOR UPDATE或者LOCK IN SHARE MODE语句,并且autocommit是enable的 | 
 
 
强行解锁
1、
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
获取到blocking_trx_id
 
2、
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
查找trx_id和上面获取到的blocking_trx_id一样的记录,获取这条记录的trx_mysql_thread_id
 
3、
kill 上面获取到的trx_mysql_thread_id,这样就把阻塞其他事务的事务线程杀掉了。
 
共享锁和排它锁
共享锁(S lock):允许持有锁的事务读取一行,select语句后加lock in share mode。
排它锁(X lock):允许持有锁的事务更新或删除一行,select语句后加for update。
共享锁和排他锁都是锁的行记录。当一个事务获取了行r的共享锁,那么另外一个事务也可以立即获取行r的共享锁,因为读取并未改变行r的数据,这种情况就是锁兼容。
但是如果有事务想获得行r的排它锁,则它必须等待其他事务释放行r上的共享锁,这种情况就是锁不兼容。二者兼容性如下表格所示:
对于select 语句,InnoDB不会加任何锁,也就是可以多个并发去进行select的操作,不会有任何的锁冲突,因为根本没有锁。
对于insert,update,delete操作,InnoDB会自动给涉及到的数据加排他锁,只有查询select需要我们手动设置排他锁。
 
意向锁
InnoDB支持多粒度锁定,允许行锁和表锁共存。例如 LOCK TABLES ... WRITE 这样的语句会获取指定表的排它锁。为了在多个粒度级别上实现锁定,InnoDB使用了意向锁。意向锁是表级锁,指示事务稍后事务对表的行需要的锁的类型(共享锁或排它锁)。意向锁有两种:
意向共享锁(IS):指示事务打算在表中的单个行上设置共享锁。如果需要对记录A加共享锁,那么此时InnoDB会先找到这张表,对该表加意向共享锁之后,再对记录A添加共享锁。
意向排它锁(IX):指示事务打算在表中的单个行上设置排他锁。如果需要对记录A加排他锁,那么此时InnoDB会先找到这张表,对该表加意向排他锁之后,再对记录A添加排他锁。
例如 SELECT ... LOCK IN SHARE MODE 设置IS,SELECT ... FOR UPDATE 设置IX。这两种意向锁都是表锁,都是系统自动添加和自动释放的,整个过程无需人工干预。
意图锁定协议如下:
在事务可以获取表中某一行上的共享锁之前,它必须首先获取表上的IS锁或比IS锁更强的锁;
在事务可以获取表中某一行上的排他锁之前,它必须首先获取表上的IX锁。
表级锁类型兼容性总结如下:
|  | X | IX | S | IS | 
| X | 冲突 | 冲突 | 冲突 | 冲突 | 
| IX | 冲突 | 兼容 | 冲突 | 兼容 | 
| S | 冲突 | 冲突 | 兼容 | 兼容 | 
| IS | 冲突 | 兼容 | 兼容 | 兼容 | 
 
如果请求事务与现有锁兼容,则授予该事务锁,但如果与现有锁冲突,则不授予该事务锁。事务等待冲突的现有锁被释放。如果锁请求与现有锁冲突,并且由于会导致死锁而无法被授予,则会发生错误。意向锁不会阻塞除全表锁请求(例如 LOCK TABLES ... WRITE)之外的任何东西,意向锁定的主要目的是显示某人锁定了一行,或者准备锁定表中的一行。
因为表锁覆盖了行锁的数据,所以表锁和行锁会产生冲突。比如A事务申请表锁,B事务申请行级锁,或者A事务申请行级锁,B事务申请表锁。这时候B事务的申请是需要被阻塞的。那么怎么判断B事务该阻塞呢?遍历表的每一行看看是否有行级锁吗?这样效率非常差。这时候就引入了意向锁。在申请行锁前,数据库自动为我们申请了对应的意向锁,因为意向锁是表锁,这时候如果再申请表锁,就自然会阻塞了。意向锁之间互相兼容,表锁(ALTER TABLE, DROP TABLE, LOCK TABLES等)会和意向锁冲突。
执行 SHOW ENGINE INNODB STATUS\G,如果有意向锁,就可以看到类似下面的信息:
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
 
记录锁
记录锁是索引记录上的锁,记录锁总是锁定索引记录,即使表没有定义索引。对于这种情况,InnoDB创建一个隐藏的聚簇索引,并使用这个索引来锁定记录。
对主键加锁,加在聚簇索引上;
对二级索引加锁,加在二级索引+聚簇索引上;
对无索引列加锁,加在聚簇索引上。
 
执行 SHOW ENGINE INNODB STATUS\G,如果有记录锁,就可以看到类似下面的信息:
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 ;;
 
取消自动提交
InnoDB下事务一旦提交或者回滚,就会自动释放事务中的锁。变量autocommit=1开启自动提交,autocommit=0关闭自动提交,默认autocommit=1。在自动提交模式下,每执行一句sql,就自动提交事务,锁也会立即释放。这种情况下无法手动控制事务的提交以及锁的释放时间。
因此,我们会在进行锁的相关操作之前,先执行(set autocommit=0)或者start transaction以关闭自动提交模式,开启手动提交模式,这样事务中的每一行sql执行完成后,锁一直不会释放,直到我们手动提交或者回滚事务,锁才会释放。
 
间隙锁(GAP)
间隙锁是对索引记录之间的间隙的锁,或对第一个索引记录之前或最后一个索引记录之后的间隙的锁,不包含索引记录本身。间隙锁是性能和并发性之间权衡的一部分,用于某些事务隔离级别,而不是其他事务隔离级别。
对于使用惟一索引锁定行以搜索惟一行的语句,不需要间隙锁(这并不包括搜索条件只包含一个复合多列惟一索引的某些列的情况,在这种情况下,确实会发生间隙锁)。如果列没有索引,或者索引不是惟一的,那么语句将产生间隙锁。
必须在REPEATABLE-READ级别才可以使用间隙锁。可以显式禁用间隙锁,如果将事务隔离级别更改为READ COMMITTED或启用innodb_locks_unsafe_for_binlog系统变量(现在已经不推荐使用该变量),就会发生这种情况。在这种情况下,对搜索和索引扫描禁用间隙锁,只用于外键约束检查和重复键检查。
如何确定间隙锁的区间?根据检索索引记录C向左寻找最靠近C的索引记录值A,作为左区间,向右寻找最靠近C的索引记录值B作为右区间,即锁定的间隙为(A,B),A<C<B,锁定的间隙除了索引记录C之间还包括第一个索引记录A之前和最后一个索引记录B之后。索引记录相同的值是根据主键升序排序的。
间隙锁的目的是为了防止幻读,其主要通过两个方面实现这个目的:
1、防止两个索引区间内有新数据被插入。
2、防止现有数据更新成两个索引区间内的数据。
 
Next-Key锁
Next-Key锁是索引记录上的记录锁(S或X)和索引记录前的间隙上的间隙上的间隙锁的组合。一般来说MySQL用的都是Next-Key锁。Next-Key锁是左开右闭的区间,例如索引包含10、11、13、20,那么可能的Next-Key锁如下:
(负无穷大, 10]
(10, 11]
(11, 13]
(13, 20]
(20, 正无穷大)
 
测试环境和数据
MySQL版本为5.7.21,部署于CentOS X86_64上,采用默认配置。
 
创建表
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`age` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_age` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8;
 
 
数据
INSERT INTO `test` (`id`,`age`) VALUES (6,1);
INSERT INTO `test` (`id`,`age`) VALUES (8,5);
INSERT INTO `test` (`id`,`age`) VALUES (10,8);
INSERT INTO `test` (`id`,`age`) VALUES (12,12);
INSERT INTO `test` (`id`,`age`) VALUES (14,14);
INSERT INTO `test` (`id`,`age`) VALUES (16,14);
INSERT INTO `test` (`id`,`age`) VALUES (18,18);
INSERT INTO `test` (`id`,`age`) VALUES (20,20);
 
+----+-----+
| id | age |
+----+-----+
| 6 | 1 |
| 8 | 5 |
| 10 | 8 |
| 12 | 12 |
| 14 | 14 |
| 16 | 14 |
| 18 | 18 |
| 20 | 20 |
+----+-----+
 
示例
启动三个会话,会话1和会话2均取消自动提交,会话3查看锁状态。
SELECT * FROM test where age=14 for update 语句的间隙锁范围(id,age)包括(12,12)到(18,18)之间的间隙,同时还会对记录age=14加排他锁。
 
一、
会话1:
SELECT * FROM test where age=14 for update;
会话2:
INSERT INTO `test` (`id`, `age`) VALUES (13, 12);
结果:
会话2阻塞。
 
二、
会话1:
SELECT * FROM test where age=14 for update;
会话2:
INSERT INTO `test` (`id`, `age`) VALUES (17, 18);
结果:
会话2阻塞。
 
三、
会话1:
SELECT * FROM test where age=14 for update;
会话2:
INSERT INTO `test` (`id`, `age`) VALUES (11, 12);
结果:
会话2执行成功。
 
四、
会话1:
SELECT * FROM test where age=14 for update;
会话2:
INSERT INTO `test` (`id`, `age`) VALUES (11, 14);
结果:
会话2阻塞。
 
五、
会话1:
SELECT * FROM test where age=14 for update;
会话2:
UPDATE `test` SET `age`=14 WHERE `id`=18;;
结果:
会话2阻塞。
 
五、
会话1:
SELECT * FROM test where age=14 for update;
会话2:
UPDATE `test` SET `age`=19 WHERE `id`=18;;
结果:
会话2执行成功。
												
												
						- MySQL · 特性分析 · innodb 锁分裂继承与迁移
		http://mysql.taobao.org/monthly/2016/06/01/ innodb行锁简介 行锁类型 LOCK_S:共享锁 LOCK_X: 排他锁 GAP类型 LOCK_GAP:只锁 ... 
- MySQL数据恢复和复制对InnoDB锁机制的影响
		MySQL通过BINLOG记录执行成功的INSERT,UPDATE,DELETE等DML语句.并由此实现数据库的恢复(point-in-time)和复制(其原理与恢复类似,通过复制和执行二进制日志使一 ... 
- mysql innodb锁简析(2)
		继续昨天的innodb锁的分析: 注:此博文参考一下地址,那里讲的也很详细.http://xm-king.iteye.com/blog/770721 mysql事务的隔离级别分为四种,隔离级别越高,数 ... 
- [转]关于MYSQL Innodb 锁行还是锁表
		关于mysql的锁行还是锁表,这个问题,今天算是有了一点头绪,mysql 中 innodb是锁行的,但是项目中居然出现了死锁,锁表的情况.为什么呢?先看一下这篇文章. 目时由于业务逻辑的需要,必须对数 ... 
- MySQL- InnoDB锁机制
		InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁.行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题.下面我们先介绍一点背景知识 ... 
- Mysql Innodb 锁机制
		latch与lock latch 可以认为是应用程序中的锁,可以称为闩锁(轻量级的锁) 因为其要求锁定的时间必须要非常短,若持续时间长,则会导致应用性能非常差,在InnoDB存储引擎中,latch又可 ... 
- MySQL InnoDB锁机制
		概述: 锁机制在程序中是最常用的机制之一,当一个程序需要多线程并行访问同一资源时,为了避免一致性问题,通常采用锁机制来处理.在数据库的操作中也有相同的问题,当两个线程同时对一条数据进行操作,为了保证数 ... 
- Mysql研磨之InnoDB行锁模式
		事务并发带来的一些问题 (1)更新丢失(LostUpdate):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题最后的更新覆盖了由其 ... 
- MySQL中InnoDB锁不住表的原因
		MySQL中InnoDB锁不住表是因为如下两个参数的设置: mysql> show variables like '%timeout%'; +-------------------------- ... 
随机推荐
	
									- java23种设计模式——三、工厂模式
			源码在我的github和gitee中获取 工厂模式 工厂模式介绍 工厂模式是我们最常用的实例化对象模式了,是用工厂方法代替new操作的一种模式.著名的Jive论坛 ,就大量使用了工厂模式,工厂模式在J ... 
- Bellman-Ford算法 例题:P3371 单源最短路径
			看到还没人用Bellman-Ford过,赶紧水一发 lz非常弱,求各位大佬轻喷qwq 洛谷题目传送门:P3371 0."松弛"操作 如果存在一条边\((u,v)\)通过中继的方式可 ... 
- OpenCV实现人脸检测
			OpenCV实现人脸检测(转载)  原文链接:https://www.cnblogs.com/mengdd/archive/2012/08/01/2619043.html 本文介绍最基本的用OpenC ... 
- 浅谈python垃圾回收机制
			引入  解释器在执行到定义变量的语法时,会申请内存空间来存放变量的值,而内存的容量是有限的,这就涉及到变量值所占用内存空间的回收问题,当一个变量值没有用了(简称垃圾)就应该将其占用的内存给回收掉,那 ... 
- Serializers组件详解
			Serializers组件 使用背景 因为每个语言都有自己的数据类型,不同语言要想数据传输,就必须指定一种各种语言通用的数据类型,如json,xml等等 序列化器允许把像查询集和模型实例这样的复杂数据 ... 
- include标签—引用文件路径
			今天给大家讲解的是include标签,在打代码的时候总会出现一些重复的样式,这个时候就可以用include标签来减少打代码的次数. 文件名index.html,代码: {% from 'macros/ ... 
- 记录laravelchina中的微信小程序教程的npm install安装报错
			npm安装报错时 npm ERR! code EIOnpm ERR! syscall symlinknpm ERR! path ../@babel/parser/bin/babel-parser.js ... 
- [PyTorch 学习笔记] 4.2 损失函数
			本章代码: https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson4/loss_function_1.py https:// ... 
- 备份etc下的内容
			echo "start backup..."sleep 3cp -av /etc/ /data/etc`date +%F`/echo "end backup"~ ... 
- Vue.use() 不一样的发现
			1.Vue.use()首先是可以接受一个函数 var a = function () { console.log(a)}Vue.use(a) // 没有问题会输出a 2.当这个a上面有install属 ...