mysql幻读、MVCC、间隙锁、意向锁(IX\IS)
IO即性能
顺序主键写性能很高,由于B+树的结构,主键如果是顺序的,则磁盘页的数据会按顺序填充,减少数据移动,随机主键则可能由于记录移动产生很多io
查询二级索引时,会再根据主键id获取数据页,产生一次磁盘io,但如果在高并发场景下,二级索引不大而被整个缓存到内存时,它甚至比主键查询还快
虽然二级索引表的读是离散的,但是索引一般字段不会太多,数据量小,索引表被整个cache到内存不是难事,而如果内存中有cache页,可以直接根据id找到记录(涉及到mysql cache方式了,还不明确),可能会更快,所以利用二级索引和自增主键反而不会引起很多的直接io,而如果使用业务主键直接读,很可能数据是贯穿整个表,数据表表的数据一般比较大,想要整个cache起来非常困难,反而在并发读取下会带来大量的cache挤占,真实io更大
引用:
作者:聿明leslie
链接:https://www.zhihu.com/question/266011062/answer/310929189
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
意向锁
锁分类:共享读锁、排他写锁,主要是为了对整表加写锁时,更搞笑,当对整表加写锁后,理论上可以修改表中的任意一行
共享读锁
多个读操作可以共享这个锁,可以并发访问
排他写锁
写锁都是排他的,一旦获得了某个锁,其他线程就无法获取对应行的写锁和读锁
如果事务A先申请了行的读锁,事务B想申请整个表的写锁,则A释放行锁之前,事务B都不会成获得整表的写锁
当需要锁行时,先向整个表申请意向(共享|排他)锁,再申请行的对应锁,这样有对表加写锁的请求时,会发现表上有意向锁,会先等待阻塞,防止表锁上的冲突,实现多粒度的控制
MVCC
mysql的写操作分为
- 修改数据区
- redo日志
如果事务A要写一个数据,需要找到对应的数据页,load到内存中,成为cache页,然后修改cache中的数据区,再记一条日志,等待日志落盘后,就返回给客户端,而非数据落盘,而此时事务可能还未提交。
当其他事务B中有请求要查询该内存页时,而A还未提交,此时显然不能直接读脏数据(cache中的),那么该如何处理呢?
此时事务A会开辟一块undo的数据区,作用是将数据回放到上一个事务的完结状态,事务B的查询操作就会访问到undo数据区,
简单流程
- 事务1:将一行数据a=1 改成 a=2,那么它会对这行数据先加排他锁,再将这行数据上一个已提交的事务verison数据copy到undo数据区
- 事务2:拿到一个全局的已提交的version去读这一行,发现被事务1修改的那一行version比查询的那一行大,则去undo区里面去查
不可重复读
一个事务中,同样的查询,两次结果不相同,就叫不可重复读。
主要说的如某个事务执行过程中,前一次查询和后一次查询,数据不一样,比如某一列的值被修改了,这种情况通常为两次查询过程中,另一个事务操作了这一行数据。
解决不可重复读
通过mvcc版本比较,解决不可重复读的问题,事务会访问到另一个事务开辟undo区域,保证后一次读和前一次读的结果相同;
再加上写锁的排他特性,保证同一时刻,只会有一个事务可以操作某一行数据
幻读
幻读主要指的是两此select(count)之类的查询结果不一样,出现了幽灵行数据。
事务A第一次执行查询后,事务B通过insert插入了一行数据,事务A再次执行查询时,发现多了一行数据。
解决幻读
把mysql的读分为
- 快照读 如两个普通的select语句
- 当前读 如select加锁读或DML修改数据
快照读(事务中的普通select)的时候,mysql通过mvcc解决幻读
间隙锁解决当前读(select * for update或update\del\insert),不只在行上加锁,而且在行与行直接的间隙加锁
next-key锁
X锁 + 间隙锁,即锁定记录行,又锁定间隙,它是innodb ,RR隔离级别下的默认锁模式
不可重复读和幻读的区别
很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。
隔离级别
https://www.cnblogs.com/windliu/p/8144202.html
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,也就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。
所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。
上面说的,是使用悲观锁机制来处理这两种问题,但是MySQL、ORACLE、PostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC(多版本并发控制)来避免这两种问题。
“读”与“读”的区别
可能有读者会疑惑,事务的隔离级别其实都是对于读数据的定义,但到了这里,就被拆成了读和写两个模块来讲解。这主要是因为MySQL中的读,和事务隔离级别中的读,是不一样的。
我们且看,在RR级别中,通过MVCC机制,虽然让数据变得可重复读,但我们读到的数据可能是历史数据,是不及时的数据,不是数据库当前的数据!这在一些对于数据的时效特别敏感的业务中,就很可能出问题。
对于这种读取历史数据的方式,我们叫它快照读 (snapshot read),而读取数据库当前版本数据的方式,叫当前读 (current read)。很显然,在MVCC中:
快照读:就是select
select * from table ....;
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update ;
delete;
事务的隔离级别实际上都是定义了当前读的级别,MySQL为了减少锁处理(包括等待其它锁)的时间,提升并发能力,引入了快照读的概念,使得select不用加锁。而update、insert这些“当前读”,就需要另外的模块来解决了。
解决幻读
解决幻读:通过行锁 + 间隙gap锁,这里说的行锁是通过mvcc实现的乐观锁,比较cache页中数据行的version和当前事务的version,如果当前version比较小,则去undo区域,在普通select读这种快照读情况下,实际上是不会有锁的,而在dml中,涉及到数据变更,会锁住被修改的行,同时,加gap锁,防止其他事务插入数据导致的幻读
唯一索引的唯一搜索(非范围查询,如t_key = 1)不用加gap锁,因为其他事务在尝试获取index-record lock时会失败
设table_a 有非唯一索引t_key,上一个已提交最新版本的全局version为10000
CREATE TABLE `table_a` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`t_key` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_t_key` (`t_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
事务1:
事务id:10001
select * from table_a where t_key = '1'; //load page into cache/memory
update table_a set t_key = '2' where t_key = '1'; //对t_key = 1 的数据行加锁,为了防止其他事务insert t_key(1),加间隙锁,并将t_key='1'的上一个版本数据行copy到undo区域,undo区域中t_key还是1,cache页中数据行版本变为10001,此时事务还没有提交,因此cache页是一个脏数据页
(事务2此时介入,查询sql select * from table_a where t_key = '1';)
....
end
事务2:
事务id:10002
select * from table_a where t_key = '1'; //查询cache页,发现上一个最新已提交version为10000,小于10001,去undo区域获取数据行
insert(t_key) table_a values(1) //事务1对table_a加了间隙锁,如索引节点 [-无穷,1]和[1,2]中间被锁住(假设有多条记录,t_key分别为1,2,3),不包括2,获取锁时都需要等待,如果table_a的t_key字段没有索引,在innodb会锁定整个表,锁住整个表的方式,通过实验,确定为锁住primary索引,包括不存在的记录
(等待事务1 end)
(没有间隙锁释放,可以插入)
间隙锁的定位方式为最近锁定行的左右区间,避免锁定没有必要的行,假如有索引数据(1,1)(3,4),(5,5)(8,5)(9,7),(主键,t_key)
update where t_key = 4时,会锁住(1)(4)之间,(4)(5)之间,而不是锁住(1) ~ (5),所以才叫间隙锁,通过分析,间隙锁应该是发生了B+树的叶子节点上,并且已经对应到行记录上了
事务1锁定了where t_key = 4 时,间隙锁t_key(1,1)(3,4)之间,(3,4)(5,5)之间,此时还需要关注主键的位置
则insert(2,3)时,它在(1,1)(3,4)之间,会阻塞
insert(4,5)时,它在(3,4)~(5,5)之间,会阻塞
insert(6,5)时,成功,因为它没在(1,1)(3,4)之间,(3,4)(5,5)之间,同样t_key都是5,一个可以成功一个不可以
如果一个事务通过update 主键id锁定了行(3,4),则另一个事务此时也无法通过t_key索引更新(3,4),因为都对应到了主键id为3的数据行
假如table_a还有一个name字段
则
select * from table_a where t_key=5 and name =‘5’ for update,将会锁住t_key的索引;
select * from table_a where name ='5' for update,会锁住primary索引
事务1:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from table_a where name='5';
Empty set (0.00 sec)
mysql> select * from table_a where name='6';
+----+-------+------+
| id | t_key | name |
+----+-------+------+
| 5 | 5 | 6 |
| 8 | 5 | 6 |
+----+-------+------+
2 rows in set (0.00 sec)
mysql> update table_a set name='5' where t_key=5;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
事务1 update操作执行,但没有commit时,执行事务2
事务2:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from table_a where t_key =5;
+----+-------+------+
| id | t_key | name |
+----+-------+------+
| 5 | 5 | 6 |
| 8 | 5 | 6 |
+----+-------+------+
2 rows in set (0.00 sec)
mysql> update table_a set name='8' where t_key=5 and name='6';
Query OK, 0 rows affected (4.64 sec)
Rows matched: 0 Changed: 0 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
最终name的值为
mysql> select * from table_a where t_key =5;
+----+-------+------+
| id | t_key | name |
+----+-------+------+
| 5 | 5 | 5 |
| 8 | 5 | 5 |
+----+-------+------+
2 rows in set (0.00 sec)
mysql幻读、MVCC、间隙锁、意向锁(IX\IS)的更多相关文章
- Mysql加锁过程详解(2)-关于mysql 幻读理解
Mysql加锁过程详解(1)-基本知识 Mysql加锁过程详解(2)-关于mysql 幻读理解 Mysql加锁过程详解(3)-关于mysql 幻读理解 Mysql加锁过程详解(4)-select fo ...
- Mysql加锁过程详解(3)-关于mysql 幻读理解
Mysql加锁过程详解(1)-基本知识 Mysql加锁过程详解(2)-关于mysql 幻读理解 Mysql加锁过程详解(3)-关于mysql 幻读理解 Mysql加锁过程详解(4)-select fo ...
- mysql 隔离级别与间隙锁等
数据库隔离级 SQL标准中DB隔离级别有: read uncommitted:可以读到其它transaction 未提交数据 read committed:可以读到其它transaction 已提交数 ...
- 浅析SQL Server在可序列化隔离级别下,防止幻读的范围锁的锁定问题
本文出处:http://www.cnblogs.com/wy123/p/7501261.html (保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错 ...
- mysql 快照读MVCC
mysql的读分快照读和当前读 快照读 是指写的同时,读不阻塞,达到并发的作用 这时候的读 是 记录的历史版本,存在于undo里,当然回滚时就的也是这个undo 当执行一条update语句时,记录本身 ...
- 《Mysql - 幻读》
一:准备 - 为了深入了解幻读,准备数据. CREATE TABLE `t` ( `id` ) NOT NULL, `c` ) DEFAULT NULL, `d` ) DEFAULT NULL, PR ...
- 关于MySQL幻读的实验
该实验基于 CentOS 7 + MySQL 5.7 进行 打开两个窗口连接到MySQL 第一个连接的事务我们命名为 T1 第二个连接的事务我们命名为 T2 T2 发生在 T1 的 O1 操作结束以 ...
- mysql幻读问题
转载:https://blog.csdn.net/u013067756/article/details/90722490 关于间隙锁:https://blog.csdn.net/sinat_27143 ...
- mysql 幻读
幻读(Phantom Read) 是指当用户读取某一范围的数据行时,B事务在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影”行.InnoDB和Falcon存储引擎通 过多版本并发 ...
随机推荐
- python中yield的用法详解——最简单,最清晰的解释(转载)
原文链接 首先我要吐槽一下,看程序的过程中遇见了yield这个关键字,然后百度的时候,发现没有一个能简单的让我懂的,讲起来真TM的都是头头是道,什么参数,什么传递的,还口口声声说自己的教程是最简单的, ...
- JVM的整体结构
整个jvm的运行流程图如上所示,首先需要进行加载class文件,然后使用类加载子系统将class翻译解析导入内存,在内存中分别导入到对应的运行时数据区,然后执行引擎开始执行,对于需要的数据在对应的区域 ...
- 深入了解Vue.js组件笔记
1.组件注册 Vue.component('name',{}) 创建的组件都是全局组件,它们在注册之后可以用在任何新创建的Vue根实例(new Vue)的模板中.第一个参数是组件的名字,第二个参数是一 ...
- JS寄快递地址智能解析
JS寄快递地址智能解析--2020年7月15日 去年做了些前端内容,最近在整理一些稍微有点用的内容,比如智能解析地址,用户只要输入:张三1351111111江苏省扬州市广陵区XX小区X楼xxx室,就能 ...
- Spring Boot第五弹,WEB开发初了解~
持续原创输出,点击上方蓝字关注我吧 目录 前言 Spring Boot 版本 前提条件(必须注意) 添加依赖 第一个接口开发 如何自定义tomcat的端口? 如何自定义项目路径? JSON格式化 日期 ...
- Eclipse 重命名工程、包、类
Eclipse版本 重命名工程,使用鼠标右键点击工程,选Refactor > Rename...(快捷键:Alt + Shift + R) 重命名包.类的操作与重命名工程一样. 其实,最简单的操 ...
- BSGS算法解析
前置芝士: 1.快速幂(用于求一个数的幂次方) 2.STL里的map(快速查找) 详解 BSGS 算法适用于解决高次同余方程 \(a^x\equiv b (mod p)\) 由费马小定理可得 x &l ...
- Android高级控件(下)
计时器(Chronometer) getBase() 基准时间 setFormat() 设置显示格式 start() 开始计时 stop() 停止计时 setOnChronometerListener ...
- STM32F103C8T6驱动WS2812b灯条
STM32F103C8T6驱动WS2812b灯条 几天小朋友到别人家玩,看上了人家的金鱼,人家就给了她一条小金鱼,有了小金鱼,怕它没氧气挂掉,买了一个氧气泵,没有东西喂它也不行,又买了一包鱼料,又因为 ...
- linxu 命令
top | grep java 统计 java 进程使用的资源比率 nohub java -jar test.war & 后台运行 test.war 程序,标准输出到 test.war 程序目 ...