众所周知innodb的锁是行级锁,这样说也没有问题,只是还可以细分而已。推荐阅读何登成大牛的博客http://hedengcheng.com/?p=771

innodb的锁有三种算法,分别如下:

Read Lock:单个记录上的锁

Gap Lock:间隙锁,锁定一个范围,但不包含记录本身

Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身

Record Lock 总是会去锁住索引记录,如果innodb存储引擎表在建立的时候没有设置任何一个索引,而且查询的时候没有使用到索引,那么这时就会导致表锁。

Next-Key Lock是结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,innodb对于行的查询都是采用这种锁定算法。例如一个索引有10,11,13,20这4

个值,那么该索引可能被Next-Key Locking的范围为:

(- &,10]

(10,11]

(13,20]

(20,+ &)

采用Next-Key Lock的锁定技术称为Next-Key Locking。这种设计的目的是为了解决幻读(Phantom Problem)。关于幻读请同学们自行了解MySQL的4个隔离级别及存在的问

题。利用这种锁定技术,锁定的不是单个值,而是一个范围。

注:当查询的索引含有唯一属性时,innodb存储引擎会对Next-Key Lock进行优化,将其降级为Record Lock,即锁住索引记录本身,而不再是范围。

测试数据如下:

root@localhost : yayun 01:00:29> create table t1 ( id int primary key);
Query OK, 0 rows affected (0.03 sec) root@localhost : yayun 01:00:31> insert into t1 ( id ) values (1),(2),(5);
Query OK, 3 rows affected (0.06 sec)
Records: 3 Duplicates: 0 Warnings: 0 root@localhost : yayun 01:01:00> select * from t1;
+----+
| id |
+----+
| 1 |
| 2 |
| 5 |
+----+
3 rows in set (0.04 sec) root@localhost : yayun 01:01:08>

看看隔离级别

root@localhost : yayun 01:20:20> show variables like '%iso%';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.12 sec) root@localhost : yayun 01:20:35>

执行相应的测试SQL语句:

会话A的操作:

root@localhost : yayun 01:20:35> begin;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 01:21:31> select * from t1 where id=5 for update;
+----+
| id |
+----+
| 5 |
+----+
1 row in set (0.08 sec) root@localhost : yayun 01:21:52>

会话B的操作(可以看见成功提交,并没有发生锁等待):

root@localhost : yayun 01:20:11> begin;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 01:22:30> insert into t1 select 4;
Query OK, 1 row affected (0.05 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 01:22:45> commit;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 01:22:49>

表t1共有1,2,5三个值。上面的示例中,在会话A首先对id=5进行X锁定。但是由于id是主键且唯一,所以锁定的仅仅是5这个值而已。而不是(2 ,5)这个范围,所以会话B中插入值4不会导致阻塞,可以立即插入提交成功。即这时候Next-Key Lock算法降级为Record Lock,仅锁住记录本身,从而提高并发性。

证实了文章开始提到的:Next-Key Lock降级为Recod Lock仅在查询的列是唯一索引的情况下。若是辅助索引,那么情况则会完全的不同。测试示例如下:

root@localhost : yayun 01:34:50> create table t2 ( id int, vid int, primary key (id), key(vid));
Query OK, 0 rows affected (0.50 sec) root@localhost : yayun 01:34:53> insert into t2 select 1,1;
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 01:35:07> insert into t2 select 3,1;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 01:35:14> insert into t2 select 5,3;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 01:35:19> insert into t2 select 7,6;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 01:35:24> insert into t2 select 10,8;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 01:35:31>

表t2的列vid列是辅助索引,在A会话执行下面的SQL语句:

root@localhost : yayun 01:38:43> begin;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 01:38:46> select * from t2 where vid=3 for update;
+----+------+
| id | vid |
+----+------+
| 5 | 3 |
+----+------+
1 row in set (0.00 sec) root@localhost : yayun 01:38:49>

很明显这时的SQL语句通过索引列vid进行查询,因此将会使用传统的Next-Key Locking技术加锁,并且由于有两个索引,需要分别进行锁定。对于聚集索引,仅对列id等于5的索引加上Record Lock,即只锁住5这个记录。而对于辅助索引,其加上的Next-Key Lock,锁定的范围是(1 ,3)的记录,特别需要注意innodb存储引擎还会对辅助索引下一个键值加上Gap Lock,即还有一个辅助索引范围为(3 ,6)的锁。因此新会话B中运行下面的SQL语句,都会被阻塞。

select * from t2 where id= lock in share mode;
insert into t2 select ,;
insert into t2 select ,;

调整一下锁超时,让测试更加方便快捷^_^

root@localhost : yayun 01:53:21> set global lock_wait_timeout=3;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 01:53:44>

会话B中操作如下:

root@localhost : yayun 01:56:40> begin;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 01:56:42> select * from t2 where id=5 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
root@localhost : yayun 01:56:56>
root@localhost : yayun 01:57:16> insert into t2 select 4,2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
root@localhost : yayun 01:57:37>
root@localhost : yayun 01:57:38> insert into t2 select 6,5;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
root@localhost : yayun 01:58:02>

可以看见上面列出的SQL都无法执行成功。被阻塞了。

第一个SQL语句不能执行是因为会话A中执行的SQL语句已经对聚集索引中列id=5的值加上了X锁,因此执行会被阻塞。第二个SQL语句,主键插入4,没有问题,但是插入的辅助索引值2在锁定的范围(1 ,3)中,因此执行同样会被阻塞。第三个SQL语句,插入的主键6没有被锁定,5也不在范围(1 ,3)之间。但插入的值5在另一个锁定的范围(3 ,6)中,故同样被锁定。相反,下面的SQL语句不会被阻塞。可以立即执行。

insert into t2 select ,;
insert into t2 select ,;
insert into t2 select ,;

在会话B中操作如下:

root@localhost : yayun ::> insert into t2 select ,;
Query OK, row affected (0.00 sec)
Records: Duplicates: Warnings: root@localhost : yayun ::> insert into t2 select ,;
Query OK, row affected (0.00 sec)
Records: Duplicates: Warnings: root@localhost : yayun ::> insert into t2 select ,;
Query OK, row affected (0.00 sec)
Records: Duplicates: Warnings: root@localhost : yayun ::>

都执行成功,没有一条被阻塞。

从上面的例子可以看到,Gap Lock的作用是为了阻止多个事务将记录插入到同一范围内,而这会导致Phantom Problem问题的产生。比如在上面的例子中,会话A中已经锁定vid=3的记录,若此时没有Gap Lock锁定(3 ,6),那么另外的用户可以插入索引列为3的记录。这将导致会话A中的用户再次执行同样的查询会返回不同的记录。这就会导致幻读(Phantom Problem)问题的产生。

我们也可以通过2种方式来显示的关闭Gap Lock:

(1)将事务的隔离级别设置为READ COMMITIED

(2)调整参数innodb_locks_unsafe_for_binlog为1

在上面的配置下,除了外键约束和唯一性检查依然需要使用Gap Lock,其余情况仅使用Record Lock进行锁定。但是通常我们不能这么干,上面的设置破坏了事务的隔离性,并且对于replication,可能会导致主从复制数据不一致。为什么会这样请参看我前面的文章。此外,从性能上来看,READ COMMITIED也不会优与默认的事务隔离级别READ REPEATABLE。

在innodb引擎中,对于INSERT操作,其会检查插入的记录的下一条记录是否会被锁定,若已经锁定,则不允许插入。

在会话A中已经锁定了表t2中vid=3的记录,即已经锁定了(1 ,3)的范围,这时若在会话B中进行如下的插入同样会被阻塞:

root@localhost : yayun 02:22:17> insert into t2 select 2,2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
root@localhost : yayun 02:22:24>

因为在辅助索引列vid上插入值为2的记录时,会监测到下一个记录3已经被锁定。如果我们执行下面的SQL则可以正常插入。

root@localhost : yayun 02:22:24> insert into t2 select 2,0;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 02:27:08>

还有两种特殊的情况需要注意:

(1)innodb表没有可使用的索引时,将采用表锁。

(2)查询仅仅是查找多个唯一索引列中的其中一个,那么查询其实就是range类型查询,innodb将依然使用Next-Key Lock算法进行锁定。

示例如下,首先先看没有索引可用的情况(1)

root@localhost : yayun 02:33:36> create table t3 ( id int, name char(20));
Query OK, 0 rows affected (0.04 sec) root@localhost : yayun 02:33:40> insert into t3 select 1,'yayun';
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 02:33:44> insert into t3 select 2,'yy';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 02:33:50> insert into t3 select 3,'mysql';
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 02:34:02>
root@localhost : yayun 02:36:35> show create table t3\G
*************************** 1. row ***************************
Table: t3
Create Table: CREATE TABLE `t3` (
`id` int(11) DEFAULT NULL,
`name` char(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec) root@localhost : yayun 02:38:53>

会话A的操作如下:

root@localhost : yayun 02:35:03> begin;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 02:35:11> update t3 set name='yayun' where id=2;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1 Changed: 1 Warnings: 0 root@localhost : yayun 02:36:35>

会话B的操作如下:

root@localhost : yayun 02:34:51> begin;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 02:37:17> update t3 set name='yayun' where id=3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
root@localhost : yayun 02:38:06>
root@localhost : yayun 02:38:08>

可以看出,只有通过索引检索数据,innodb才会采用行锁,否则,innodb将会使用表锁。生产环境一定要注意。

下面看看对于查询的列是唯一索引列,且唯一索引列由多个列组成,innodb采用什么锁算法。

示例如下:

root@localhost : yayun 02:43:49> create table t4 ( id int , uid int, unique key(id,uid))engine=innodb;
Query OK, 0 rows affected (0.04 sec) root@localhost : yayun 02:45:15> insert into t4 select 1,2;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 02:45:45> insert into t4 select 1,3;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 02:45:48> insert into t4 select 1,5;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 02:45:51> insert into t4 select 1,8;
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0 root@localhost : yayun 02:45:54>

上面我创建了一个唯一索引,是由id,uid两个字段组成。

会话A的操作如下:

root@localhost : yayun 02:47:31> begin;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 02:47:36> select * from t4 where uid=5 for update;
+------+------+
| id | uid |
+------+------+
| 1 | 5 |
+------+------+
1 row in set (0.03 sec) root@localhost : yayun 02:49:30>

会话B的操作如下:

root@localhost : yayun 02:43:56> begin;
Query OK, 0 rows affected (0.00 sec) root@localhost : yayun 02:50:01> insert into t4 select 1,2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
root@localhost : yayun 02:50:58>
root@localhost : yayun 02:50:59> insert into t4 select 1,4;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
root@localhost : yayun 02:51:19>
root@localhost : yayun 02:51:30> insert into t4 select 1,6;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
root@localhost : yayun 02:51:45>
root@localhost : yayun 02:51:46> insert into t4 select 1,8;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
root@localhost : yayun 02:51:53>

可见依然采用的是Next-Key Lock进行锁定的。不再重复。说了这么多相信大家都有一个初步印象了,当你发现生产环境中一个update语句就执行了30s,40s的时候,是时候好好检查一下了。因为真正的执行时间估计很短,时间是耗费在锁等待上面了。

总结:

innodb引擎有三种锁的算法设计:
Record lock:对单个索引项加锁
Gap lock:间隙锁,对索引项之间的"间隙",第一条记录前的"间隙"或最后一条记录后的"    间隙"加锁,不包括索引项本身
Next-key lock:Gap lock+Next-key lock 锁定索引项范围。对记录及其前面的间隙加锁
 
注意:
对于唯一索引,其加上的是Record Lock,仅锁住记录本身。但也有特别情况,那就是唯一索引由多个列组成,而查询仅是查找多个唯一索引列中的其中一个,那么加锁的情况依然是Next-key lock。
 
对于辅助索引,其加上的是Next-Key Lock,锁定的是范围,包含记录本身。
另外如果使用相等的条件给一个不存在的记录加锁,innodb也会使用Next-key lock
 
特别注意:
innodb存储引擎是通过给索引上的索引项加锁来实现,这意味着:只有通过索引条件检索数据,innodb才会使用行锁,否则,innodb将使用表锁。当然这种说法是在表没有主键或者没有任何索引的情况下。如果一个表有主键,没有其他的索引,检索条件又不是主键,SQL会走聚簇索引的全扫描进行过滤,由于过滤是由MySQL Server层面进行的。因此每条记录,无论是否满足条件,都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录上的加锁/放锁动作不会省略。同时,优化也违背了2PL的约束。

参考资料

<<MySQL技术内幕--InnoDB存储引擎第2版>>

InnoDB Lock的更多相关文章

  1. 排查mysql innodb Lock wait timeout exceeded; try restarting transaction的问题

    OMG写的时候崩溃了一次. 触发关注这个问题的事情是 我们在使用pt-online-schedule 改表的时候总是拿不到锁,并且报出mysql innodb Lock wait timeout ex ...

  2. InnoDB Lock浅谈

    数据库使用锁是为了支持更好的并发,提供数据的完整性和一致性.InnoDB是一个支持行锁的存储引擎,锁的类型有:共享锁(S).排他锁(X).意向共享(IS).意向排他(IX).为了提供更好的并发,Inn ...

  3. innodb Lock wait timeout exceeded;

    当出现:ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction,要解决是一件麻烦的事情:特别是当一个SQL ...

  4. InnoDB锁机制分析

    InnoDB锁机制常常困扰大家,不同的条件下往往表现出不同的锁竞争,在实际工作中经常要分析各种锁超时.死锁的问题.本文通过不同条件下的实验,利用InnoDB系统给出的各种信息,分析了锁的工作机制.通过 ...

  5. [转载] 数据库分析手记 —— InnoDB锁机制分析

    作者:倪煜 InnoDB锁机制常常困扰大家,不同的条件下往往表现出不同的锁竞争,在实际工作中经常要分析各种锁超时.死锁的问题.本文通过不同条件下的实验,利用InnoDB系统给出的各种信息,分析了锁的工 ...

  6. MySQL · 引擎特性 · InnoDB 事务锁简介

    https://yq.aliyun.com/articles/4270# zhaiwx_yinfeng 2016-02-02 19:00:43 浏览2194 评论0 mysql innodb lock ...

  7. 初步认知MySQL metadata lock(MDL)

    http://blog.itpub.net/26515977/viewspace-1208250/ 概述 随着5.5.3引入MDL,更多的Query被“Waiting for table metada ...

  8. Metadata Lock原理3

      http://blog.itpub.net/26515977/viewspace-1208250/   腾讯工程师 随着5.5.3引入MDL,更多的Query被“Waiting for table ...

  9. 转 MYSQL InnoDB Record, Gap, and Next-Key Locks

    http://dev.mysql.com/doc/refman/5.0/en/innodb-record-level-locks.html InnoDB has several types of re ...

随机推荐

  1. php一句话木马

    一句话木马就是只需要一行代码的木马,短短一行代码,就能做到和大马相当的功能. 为了绕过waf的检测,一句话木马出现了无数中变形,但本质是不变的:木马的函数执行了发送的命令. 通过GET .POST . ...

  2. 8 -- 深入使用Spring -- 5...2 使用@Cacheable执行缓存

    8.5.2 使用@Cacheable执行缓存 @Cacheable可用于修饰类或修饰方法,当使用@Cacheable修饰类时,用于告诉Spring在类级别上进行缓存 ------ 程序调用该类的实例的 ...

  3. ScrollView fillViewport

    话不多说直接上图

  4. 【mysql】MySQL以逗号隔开的字符串查询方式整理

    1 单个值查询使用函数进行处理,FIND_IN_SET()实用 SELECT * FROM `by_info` WHERE FIND_IN_SET(',type_id); 2 多个值模糊查询,使用正则 ...

  5. Ubuntu屏幕录像软件推荐-Kazam

    sudo apt-get install kazam https://blog.csdn.net/weixin_40153532/article/details/79337630

  6. inittab 解析

    Linux完成内核(Kernel)引导后,会由init初始化进程调用/etc/inittab配置文件(ps -aux | less,init进程号为始终为1,是所有系统进程的起点,init进程也有一个 ...

  7. kafka+zookeeper环境配置(linux环境单机版)

    版本: CentOS-6.5-x86_64 zookeeper-3.4.6 kafka_2.10-0.10.1.0 一.zookeeper下载与安装 1)下载 $ wget http://mirror ...

  8. 学习vue 2.x源码笔记

    1.响应式原理: 核心:Object.defineProperty,用法如下: var obj1 = {}; var initValue = 'hello'; Object.definePropert ...

  9. 夺冠概率|2012年蓝桥杯B组题解析第九题-fishers

    (17')夺冠概率 足球比赛具有一定程度的偶然性,弱队也有战胜强队的可能. 假设有甲.乙.丙.丁四个球队.根据他们过去比赛的成绩,得出每个队与另一个队对阵时取胜的概率表: 甲 乙 丙 丁 甲 - 0. ...

  10. mysql中的schema 等价于database,相当于一个数据库

    MySQL 中 Schema 等价于 数据库. mysql> SELECT -> SCHEMA_NAME, -> DEFAULT_CHARACTER_SET_NAME, -> ...