概述

前面两篇文章介绍了MySQL的全局锁表级锁,今天就介绍一下MySQL的行锁。

MySQL的行锁是各个引擎内部实现的,不是所有的引擎支持行锁,例如MyISAM就不支持行锁。

不支持行锁就意味着在并发操作时,就要使用表锁,在任意时刻都只能有一个更新操作在执行,这样会影响业务的并发性。这也是为什么MyISAM会被InnoDB取代的原因之一。

行锁是锁里最小粒度的锁,InnoDB引擎里的行锁的实现算法有三种:

  • Record Lock:行锁,锁住记录本身
  • Gap Lock:间隙锁,锁住某个范围,但不包括记录本身
  • Next-Key Lock:Record Lock + Gap Lock,既锁范围,又锁记录

InnoDB是使用Next-Key Lock来解决幻读问题的。

什么是幻读?

我们看一下这个例子,有一个表 t,插入部分数据。

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);



图1 假设只在id=5这一行加行锁

有三个会话并发执行,Session A在T1,T3,T5时刻分别查询同一个语句,出现不同的结果。其中Q3读到的id=1这一行的现象,被称为幻读。

幻读,指同一个事务中,两次相同的查询操作,得到的结果行数不一样。

这里要对“幻读”做两点说明:

  1. 在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此幻读在“当前读”下才会出现。
  2. 上面的Session B的修改结果,被Session A之后的select语句用“当前读”看到了,不能称为幻读。幻读仅专指“新插入的行”。

根据数据可见性规则分析,这三个查询都加了for update,都是“当前读”,符合数据可见性规则。

这么看来,好像没什么问题,是不是真的没有问题呢?

不,这里还真就有问题。

幻读有什么问题?

语义上不一致

Session A在T1时刻就声明了,“我要把所有d=5的行锁住,不准别的事务进行读写操作”。而实际上,这个语义被破坏了。

上面的例子可能还看不太出来,我们给Session B和Session C分别加两个语句,再看看会出现什么现象。




图2 假设只在id=5这一行加行锁--语义被破坏

Session B的第二条语句update t set c = 5 where id=0,语义是“我要把id=0、d=5的这一行的c的值改成了5”。

由于在T1时刻,Session A还只是给t=5这一行加了行锁,并没有给id=0这一行加锁。因此Session B在T2时刻,是可以执行这条语句的。

同理,Session C对id=1这行的修改,一样是破坏了Q1的加锁声明。

数据上不一致

其次是造成数据上不一致。锁的设计就是为了保证数据一致性的,这里的一致性除了内部数据在此刻的一致性外,还包含数据和日志在逻辑上的一致性。




图 3 假设只在id=5这一行加行锁--数据一致性问题

我们来分析一下图3执行完成后,数据库的数据是什么:

  1. 经过T1时刻,id=5这一行变成 (5,5,100),当然这个结果最终是在T6时刻正式提交的
  2. 经过T2时刻,id=0这一行变成(0,5,5);
  3. 经过T4时刻,表里面多了一行(1,5,5);

我们再来看看binlog的内容:

// session B
update t set d=5 where id=0;
update t set c=5 where id=0; // session C
insert into t values(1,1,5);
update t set c=5 where id=1; update t set d=100 where d=5;

按照这个语句序列,这三行的结果变成:(0,5,100),(1,5,100),(5,5,100)。

也就是说id=0和id=1这两行,发生了数据不一致。这个问题很严重,是不行的。

那究竟这个数据不一致是怎样引入的呢?




图 4 假设扫描到的行都被加上了行锁

假设我们对扫描到的行都加上行锁,来看看图4执行后会出现什么现象。

  1. 经过T1时刻,id=5这一行变成 (5,5,100),当然这个结果最终是在T6时刻正式提交的
  2. 经过T2时刻,Session B被阻塞,等到T6时刻Session A释放锁才能执行;
  3. 经过T4时刻,表里面多了一行(1,5,5);
  4. 经过T6时刻,id=1这一行变成(1,5,100);

id=1这一行还是出现数据不一致的问题。即使把所有的记录都加上锁,还是阻止不了新插入的记录。

如何解决幻读?

我们现在知道产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB引入了间隙锁(Gap Lock)。

前面介绍过,间隙锁,锁住某个范围,但不包括记录本身。比如前面说到的表t,初始化有6条记录,这就产生了7个间隙。




图 5 表t主键索引上的行锁和间隙锁

当你执行select * from t where d=5 for update的时候,就不止是给数据库中6个记录加了行锁,还同时加了7个间隙锁。这样就确保了无法再插入新的记录。

也就是说这时候,在一行行扫描的过程中,不仅给行加上行锁,还给行两边的空隙也加上间隙锁。

我们回到上面的图4,再来看看加上间隙锁后,执行的效果如何。

  1. 经过T1时刻,id=5这一行变成 (5,5,100),当然这个结果最终是在T6时刻正式提交的。因为select * from t where d=6 for update,对6个记录加了行锁,同时加了7个间隙锁。
  2. 经过T2时刻,Session B被阻塞,因为id=0这一行被锁;
  3. 经过T4时刻,Session C被阻塞,因为主键索引上加了间隙锁(0,5),所以id=1这个值无法被插入;

Session B和Session C都要等待Session A释放锁后才能继续执行,这样就解决了幻读的问题。

行锁保证更新行,间隙锁保证插入行,而行锁+间隙锁=Next-Key Lock,也就是本文开头说到的,InnoDB是通过Next-Key Lock来解决幻读问题的。

但是间隙锁的引入,可能会导致同样的语句锁住更大的范围,这会影响并发度的。比如上面的select * from t where d=5 for update,相当于加了表锁。

参考资料

MySQL锁(三)行锁:幻读是什么?如何解决幻读?的更多相关文章

  1. [转]MySQL 表锁和行锁机制

    本文转自:http://www.cnblogs.com/itdragon/p/8194622.html MySQL 表锁和行锁机制 行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整 ...

  2. MySQL中的锁(表锁、行锁)

    锁是计算机协调多个进程或纯线程并发访问某一资源的机制.在数据库中,除传统的计算资源(CPU.RAM.I/O)的争用以外,数据也是一种供许多用户共享的资源.如何保证数据并发访问的一致性.有效性是所在有数 ...

  3. Mysql表锁、行锁、页锁

    参考 http://www.jb51.net/article/50047.htm <MySQL行级锁.表级锁.页级锁详细介绍> 页级:引擎 BDB.表级:引擎 MyISAM , 理解为锁住 ...

  4. 悲观锁,乐观锁,排他锁,行锁----MYSQL

    在说具体的锁结构时,先思考一个问题,那就是为什么要上锁?然后我要如何选择锁?锁具体如何实现? 在文章得末尾我给出了我的个人答案. 一.什么是悲观锁? 1.悲观锁就是在操作数据时,认为此操作会出现数据冲 ...

  5. MySQL表锁和行锁

    锁粒度 MySQL 不同的存储引擎支持不同的锁机制,所有的存储引擎都以自己的方式显现了锁机制,服务器层完全不了解存储引擎中的锁实现: InnoDB 存储引擎既支持行级锁(row-level locki ...

  6. MySql中的锁(表锁,行锁)

    锁是计算机协调多个进程或春线程并发访问某一资源的机制.在数据库中,除传统的计算资源(CPU,RAM,I/O)的争用之外,数据也是一种工许多用户共享的资源.如何保证数据并发访问的一致性,有效性是所有数据 ...

  7. 三分钟入门 InnoDB 存储引擎中的表锁和行锁

    各位对 "锁" 这个概念应该都不是很陌生吧,Java 语言中就提供了两种锁:内置的 synchronized 锁和 Lock 接口,使用锁的目的就是管理对共享资源的并发访问,保证数 ...

  8. mysql的innodb 引擎 表锁与行锁

    innodb 引擎 行锁与表锁 行锁与表锁是基于索引来说的(且索引要生效) 不带索引 (表锁)要全表扫描 1. 执行select @@autocommit; 查看结果 0是不自动提交事务,1是自动提交 ...

  9. MySQL的中的全局锁、表级锁、行锁

    MySQL的中的全局锁.表级锁.行锁 学习极客时间-林晓彬老师-MySQL实战45讲 学习整理 全局锁 对整个数据库实例加锁.通过使用Flush tables with read lock (FTWR ...

  10. innodb 表锁和行锁

    表锁  表锁相关结构: table->locks:数据字典table保存这个表上的所有表锁信息 trx->lock.table_locks:每个事务trx保存该事务所加的所有表锁信息 tr ...

随机推荐

  1. [原题复现+审计][ZJCTF 2019] WEB NiZhuanSiWei(反序列化、PHP伪协议、数组绕过)

    简介  原题复现:https://github.com/CTFTraining/zjctf_2019_final_web_nizhuansiwei/  考察知识点:反序列化.PHP伪协议.数组绕过   ...

  2. webug第十六关:明天双十一

    ---恢复内容开始--- 第十六关:明天双十一 不说了...只能看着源码做出来 ---恢复内容结束---

  3. 使用IDM批量抓取音效素材下载

    IDM下载器的站点抓取功能,能够抓取网站上的图片.音频.视频.PDF.压缩包等等文件.更重要的是,能够实现批量抓取操作,省时省力.今天就来看一下,如何用IDM巧妙的批量抓取音效素材. 1.进入音效合辑 ...

  4. [工具推荐]制作基于Dash的本地文档方便搜索文档api和内容

    [版权声明]:本文章由danvid发布于http://danvid.cnblogs.com/,如需转载或部分使用请注明出处 最近在看es的文档,发现查起api来真的很麻烦,很多现在开源的文档都没有查询 ...

  5. C语言讲义——冒泡排序(bubble sort)

    冒泡排序三步走: 循环 交换 回一手 一个数和其它数比较(循环) 每个数都要做这种比较(再一层循环) 准备工作 #include <stdio.h> void sort(int arr[] ...

  6. Java基础教程——变量

    变量 变量(variable)可以理解为一个"有名称的容器",用于装各种不同类型的数据.编程人员通过对变量的访问和修改,操作内存中的数据. 对变量的理解:https://www.c ...

  7. 由OptionalLong想到的拆装箱问题

    包装类型为null的时候时候拆箱会报空指针

  8. CentOS下Python尝试

    打印一个爱心 #猴赛雷 print'\n'.join([''.join([('AndyLove'[(x-y)%8]if((x*0.05)**2+(y*0.1)**2-1)**3-(x*0.05)**2 ...

  9. 教学之Treap

    放在前面的话 本蒟蒻因为最近的题目总是搞点奇奇怪怪的平衡树,就去学了下\(Treap\) 现在来总结一下 由于本人是个蒟蒻,本文可能有部分错误,麻烦各位读者大佬在评论区提醒 什么是\(Treap\) ...

  10. 大数据开发——Hive笔记

    写在前面 hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的sql查询功能,可以将sql语句转换为MapReduce任务进行运行.Hive的运行原理- ...