隔离级别的产生

在串型执行的条件下,数据修改的顺序是固定的、可预期的结果,但是并发执行的情况下,数据的修改是不可预期的,也不固定,为了实现数据修改在并发执行的情况下得到一个固定、可预期的结果,由此产生了隔离级别。

所以隔离级别的作用是用来平衡数据库并发访问与数据一致性的方法。

事务的4种隔离级别READ UNCOMMITTED       未提交读,可以读取未提交的数据。READ COMMITTED         已提交读,对于锁定读(select with for update 或者 for share)、update 和 delete 语句,                       InnoDB 仅锁定索引记录,而不锁定它们之间的间隙,因此允许在锁定的记录旁边自由插入新记录。                       Gap locking 仅用于外键约束检查和重复键检查。REPEATABLE READ        可重复读,事务中的一致性读取读取的是事务第一次读取所建立的快照。SERIALIZABLE           序列化在了解了 4 种隔离级别的需求后,在采用锁控制隔离级别的基础上,我们需要了解加锁的对象(数据本身&间隙),以及了解整个数据范围的全集组成。

数据范围全集组成

SQL 语句根据条件判断不需要扫描的数据范围(不加锁);

SQL 语句根据条件扫描到的可能需要加锁的数据范围;

以单个数据范围为例,数据范围全集包含:(数据范围不一定是连续的值,也可能是间隔的值组成)

1. 数据已经填充了整个数据范围:(被完全填充的数据范围,不存在数据间隙)整形,对值具有唯一约束条件的数据范围 1~5 ,

已有数据1、2、3、4、5,此时数据范围已被完全填充;

整形,对值具有唯一约束条件的数据范围 1 和 5 ,

已有数据1、5,此时数据范围已被完全填充;

2. 数据填充了部分数据范围:(未被完全填充的数据范围,是存在数据间隙)整形的数据范围 1~5 ,

已有数据 1、2、3、4、5,但是因为没有唯一约束,

所以数据范围可以继续被 1~5 的数据重复填充;

整形,具有唯一约束条件的数据范围 1~5 ,

已有数据 2,5,此时数据范围未被完全填充,还可以填充 1、3、4 ;

3. 数据范围内没有任何数据(存在间隙)

如下:整形的数据范围 1~5 ,数据范围内当前没有任何数据。在了解了数据全集的组成后,我们再来看看事务并发时,会带来的问题。

无控制的并发所带来的问题

并发事务如果不加以控制的话会带来一些问题,主要包括以下几种情况。

1. 范围内已有数据更改导致的:更新丢失:当多个事务选择了同一行,然后基于最初选定的值更新该行时,

由于每个事物不知道其他事务的存在,最后的更新就会覆盖其他事务所做的更新;

脏读:一个事务正在对一条记录做修改,这个事务完成并提交前,这条记录就处于不一致状态。

这时,另外一个事务也来读取同一条记录,如果不加控制,

第二个事务读取了这些“脏”数据,并据此做了进一步的处理,就会产生提交的数据依赖关系。

这种现象就叫“脏读”。

2. 范围内数据量发生了变化导致:不可重复读:一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,

却发现其读出的数据已经发生了改变,或者某些记录已经被删除了。

这种现象就叫“不可重复读”。

幻读:一个事务按相同的查询条件重新读取以前检索过的数据,

却发现其他事务插入了满足其查询条件的新数据,这种现象称为“幻读”。

可以简单的认为满足条件的数据量变化了。因为无控制的并发会带来一系列的问题,这些问题会导致无法满足我们所需要的结果。 因此我们需要控制并发,以实现我们所期望的结果(隔离级别)。

MySQL 隔离级别的实现

InnoDB 通过加锁的策略来支持这些隔离级别。

行锁包含:Record Locks

索引记录锁,索引记录锁始终锁定索引记录,即使表中未定义索引,

这种情况下,InnoDB 创建一个隐藏的聚簇索引,并使用该索引进行记录锁定。

Gap Locks

间隙锁是索引记录之间的间隙上的锁,或者对第一条记录之前或者最后一条记录之后的锁。

间隙锁是性能和并发之间权衡的一部分。

对于无间隙的数据范围不需要间隙锁,因为没有间隙。

Next-Key Locks

索引记录上的记录锁和索引记录之前的 gap lock 的组合。

假设索引包含 10、11、13 和 20。

可能的next-key locks包括以下间隔,其中圆括号表示不包含间隔端点,方括号表示包含端点:(负无穷大, 10]    (10, 11]    (11, 13]    (13, 20]    (20, 正无穷大)        对于最后一个间隔,next-key将会锁定索引中最大值的上方,

"上确界"伪记录的值高于索引中任何实际值。

上确界不是一个真正的索引记录,因此,实际上,这个 next-key 只锁定最大索引值之后的间隙。

基于此,当获取的数据范围中,数据已填充了所有的数据范围,那么此时是不存在间隙的,也就不需要 gap lock。

对于数据范围内存在间隙的,需要根据隔离级别确认是否对间隙加锁。

默认的 REPEATABLE READ 隔离级别,为了保证可重复读,除了对数据本身加锁以外,还需要对数据间隙加锁。

READ COMMITTED 已提交读,不匹配行的记录锁在 MySQL 评估了 where 条件后释放。

对于 update 语句,InnoDB 执行 "semi-consistent" 读取,这样它会将最新提交的版本返回到 MySQL,

以便 MySQL 可以确定该行是否与 update 的 where 条件相匹配。现在我们来验证以下 MySQL 对于隔离级别的实现是否符合预期。

场景演示

下面整理几种场景,确定其所加锁:

数据准备:CREATE TABLE`t`(

`a`int(11) NOT NULL,

`b`int(11) DEFAULT NULL,

`c`int(11) DEFAULT NULL,

`d`int(11) DEFAULT NULL,

`e`int(11) DEFAULT NULL,

PRIMARY KEY (`a`),

UNIQUE KEY`c`(`c`,`d`,`e`),

KEY`b`(`b`)

);

insertintot values (1,2,1,2,3),(2,2,1,2,4),(3,3,1,2,5),(4,4,2,3,4),(5,5,3,3,5),(6,7,4,5,5),(7,10,5,6,7),(8,13,5,3,1),(9,20,6,9,10),(10,23,7,7,7) ;

说明:session ASQL1SQL2 ....session BSQLNSQLN+1...上面的语句都是按照时间顺序排序。所有的测试数据都是基于数据准备好的原始数据,每次测试完成,请自我修复现场。

场景一

rr 模式 + 唯一索引筛选:

数据:数据范围已被数据完全填充(1)

已有数据的更改

session A

start transaction ;

update tseta=11wherea=3;# 持有Record Locks(a=3)

session B

update tseta=12wherea=3;# 等待a=3的Record Locks,

直到session A将其释放/等锁超时

数据范围有数据,但未被完全填充(2)

已有数据的更改

session A

start transaction ;

update tsetb=11wherea>=10;# 持有Record Locks (a=10),

有Next-KeyLocks(a>10)

session B

update tsetb=12wherea=10;# 等待a=10的Record Locks,

直到session A将其释放/等锁超时

间隙的填充

session A

start transaction ;

update tsetb=11wherea>=10;# 持有Record Locks (a=10),

有Next-KeyLocks(a>10)

session B

insertintot values(11,1,21,1,1);# 插入等待,

因为存在a>10的Next-KeyLocks

数据范围内没有数据(3)

间隙的填充

session A

start transaction ;

update tsetb=11wherea>=100anda<=200;# 存在Gap Locks

session B

insertintot values(150,1,21,1,1);# 插入等待,

因为存在(10,无穷大)的GapLocks,

a最大的一个值是10

insertintot values(11,1,1,2,2) ;# 插入等待,

因为存在(10,无穷大)的Gap.Locks,

a最大的一个值是10

update tsetb=8wherea=10;# 更改成功,

因为session A并未持有RecordLocks

update tsetb=23wherea=10;# 恢复现场

场景二

rc 模式 + 唯一索引筛选:

数据:数据范围已被数据完全填充(4)

已有数据的更改

session A

start transaction ;

update tseta=11wherea=3;# 持有Record Locks(a=3)

session B

update tseta=12wherea=3;# 等待a=3的Record Locks,

直到session A将其释放/等锁超时

数据范围有数据,但未被完全填充(5)

已有数据的更改

session A

start transaction ;

update tsetb=11wherea>=10;# 持有Record Locks (a=10),

无Next-KeyLocks(a>10)

session B

update tsetb=12wherea=10;# 等待a=10的Record Locks,

直到session A将其释放/等锁超时

间隙的填充

session A

start transaction ;

update tsetb=11wherea>=10;# 持有Record Locks (a=10),

无Next-KeyLocks(a>10)

session B

insertintot values(11,1,21,1,1);# 插入成功

deletefromtwherea=11;# 恢复现场

数据范围内没有数据(6)

间隙的填充

session A

start transaction ;

update tsetb=11wherea>=100anda<=200;# 无对应的索引,

所以无RecordLocks,

无Next-KeyLocks

session B

insertintot values(150,1,21,1,1);# 插入成功

deletefromtwherea=150;# 恢复现场

场景三

rr 模式 + 非唯一索引筛选:(非唯一索引筛选的情况下,不存在数据完全填充的场景)

数据:数据范围有数据,但未被完全填充(7)

**已有数据的更改**

session A

start transaction ;

update tsete=7whereb=2ande=4;# 获取非唯一索引,命中b=2的索引值,

b=2的索引值对应多条记录。

此时有RecordLocks,加在非唯一索引上

session B

update tsete=7whereb=2ande=10;# session A已获取了b=2的 Record Locks ,

session B等待session A将其释放/等锁超时

即使该条件命中的是空记录

间隙的填充

session A

start transaction ;

update tsetd=6whereb>=5andb<=7;# 获取了b=5和 b=7的 Record Locks,

和(5,7)的GapLocks

session B

insertintot values(11,6,11,12,13) ;# 插入b=6的记录,插入等待,

存在b的数据范围(5,7)的GapLocks。

数据范围内没有数据(8)

间隙的填充

session A

start transaction ;

update tsetb=100whereb>=120;# b>=120命中了空的数据范围,

所以无RecordLocks,

但存在(23,正无穷)的GapLocks

session B

insertintot values(200,200,200,200,200) ;# 插入等待,

等待(23,正无穷)的GapLocks的释放

insertintot values(100,24,3,4,60) ;# 插入等待,

等待(23,正无穷)的Gaplocks的释放

update tsetb=12whereb=23;# 更新成功,

因为session A并未获取RecordLocks,

所以不会阻止已有行的更新

update tsetb=23whereb=12;# 恢复现场

场景四

rc 模式 + 非唯一索引筛选:(非唯一索引筛选的情况下,不存在数据完全填充的场景)

数据:数据范围有数据,但未被完全填充(9)

已有数据的更改

session A

start transaction ;

update tsete=7whereb=2ande=4;# 获取非唯一索引,命中b=2的索引值,

b=2的索引值对应多条记录。

此时有RecordLocks,加在非唯一索引上

session B

update tsete=7whereb=2ande=10;# session A已获取了b=2的Record Locks ,

session B等待session A将其释放/等锁超时,

即使该条件命中的是空记录

间隙的填充

session A

start transaction ;

update tsetd=6whereb>=5andb<=7;# 获取了b=5和 b=7的 Record Locks,

无Next-KeyLocks

session B

insertintot values(11,6,11,12,13) ;# 插入成功,因为无 Next-Key Locks

数据范围内没有数据(10)

间隙的填充

session A

start transaction ;

update tsetb=100whereb>=120;# b>=120命中了空的数据范围,

所以无RecordLocks,

无Next-KeyLocks

session B

insertintot values(200,200,200,200,200) ;# 插入成功,因为无Next-Key Locks

场景五

rr 模式 + 无索引筛选:(无索引筛选的情况下,不存在数据完全填充的场景)

数据:数据范围有数据,但未被完全填充(11)

已有数据的更改

session A

start transaction ;

update tsetc=100whered=2;# d=2 对应a=1、a=2、a=3的记录,

因为where条件未使用索引,故只能全表扫描,

并对所有行加RecordLocks,

并且在间隙中加上GapLocks

session B

update tsetb=100wherea=1;# 等待session A获取的a=1的X锁

间隙的填充

因为where条件并未使用索引,所以最终加锁都回归到了对基表的聚簇索引加锁,

但是where条件未使用索引,自然更无唯一约束,

所以逻辑上可以认为除where命中的行以外的其他范围都是间隙。

而实际上因为通过聚簇索引加锁,所以在每两个聚簇索引之间才会加上GapLocks。

session A

start transaction ;

update tsetc=100whered=2;# d=2 对应a=1、a=2、a=3的记录,

因为where条件未使用索引,故只能全表扫描,

并对所有聚簇索引加RecordLocks,

并且加上Next-KeyLocks

session B

update tsetb=50wherea=8;# 等待session A获取的a=8的X锁

insertintot values(110,30,30,40,500);# 等待Next-Key Locks

数据范围内没有数据(12)

间隙的填充

因为where条件并未使用索引,所以最终加锁都回归到了对基表的聚簇索引加锁,

但是where条件未使用索引,自然更无唯一约束,

所以逻辑上可以认为除where命中的行以外的其他范围都是间隙。

而实际上因为通过聚簇索引加锁,所以在每两个聚簇索引之间才会加上GapLocks。

session A

start transaction ;

update tsetc=100whered=20;#  因为where条件未使用索引,

故只能全表扫描,

并对所有聚簇索引加RecordLocks,

并且加上Next-KeyLocks

session B

update tsetb=50wherea=8;# 等待session A获取的a=8的X锁

insertintot values (100,3,100,20,4) ;# 等待Next-Key Locks

场景六

rc 模式 + 无索引筛选:(无索引筛选的情况下,不存在数据完全填充的场景)

数据:数据范围有数据,但未被完全填充(13)

已有数据的更改

session A

start transaction ;

update tsetc=100whered=2;# 对应a=1、a=2、a=3的记录,

因为where条件未使用索引,故只能全表扫描,

并对a=1、a=2、a=3聚簇索引加RecordLocks,

但是无Next-KeyLocks

session B

update tsetb=100wherea=1;# 等待session A获取的a=1的Record Locks

间隙的填充

因为where条件并未使用索引,所以最终加锁都回归到了对基表的聚簇索引加锁,

但是where条件未使用索引,自然更无唯一约束,

所以逻辑上可以认为除where命中的行以外的其他范围都是间隙。

而实际上因为通过聚簇索引加锁,所以在每两个聚簇索引之间才会加上GapLocks。

session A

start transaction ;

update tsetc=100whered=2;# 对应a=1、a=2、a=3的记录,

因为where条件未使用索引,故只能全表扫描,

并对a=1、a=2、a=3聚簇索引加RecordLocks,

但是无Next-KeyLocks

session B

update tsetb=50wherea=8;# 成功,因为a=8并未被session A加锁

insertintot values(110,30,30,2,500);# 成功,因为不存在Gap Locks &

next-KeyLocks

数据范围内没有数据(14)

间隙的填充*

session A

start transaction ;

update tsetc=100whered=20;# 无Record Locks,

也无Next-KeyLocks

session B

insertintot values (100,3,100,20,4) ;# 成功

deletefromtwherea=100;# 恢复现场

总结&延展:

唯一索引存在唯一约束,所以变更后的数据若违反了唯一约束的原则,则会失败。

当 where 条件使用二级索引筛选数据时,会对二级索引命中的条目和对应的聚簇索引都加锁;所以其他事务变更命中加锁的聚簇索引时,都会等待锁。

行锁的增加是一行一行增加的,所以可能导致并发情况下死锁的发生。

例如,

在 session A 对符合条件的某聚簇索引加锁时,可能 session B 已持有该聚簇索引的 Record Locks,而 session B 正在等待 session A 已持有的某聚簇索引的 Record Locks。

session A 和 session B 是通过两个不相干的二级索引定位到的聚簇索引。

session A 通过索引 idA,session B通过索引 idB。

当 where 条件获取的数据无间隙时,无论隔离级别为 rc 或 rr,都不会存在间隙锁。

比如通过唯一索引获取到了已完全填充的数据范围,此时不需要间隙锁。

间隙锁的目的在于阻止数据插入间隙,所以无论是通过 insert 或 update 变更导致的间隙内数据的存在,都会被阻止。

rc 隔离级别模式下,查询和索引扫描将禁用 gap locking,此时 gap locking 仅用于外键约束检查和重复键检查(主要是唯一性检查)。

rr 模式下,为了防止幻读,会加上 Gap Locks。

事务中,SQL 开始则加锁,事务结束才释放锁。

就锁类型而言,应该有优化锁,锁升级等,例如rr模式未使用索引查询的情况下,是否可以直接升级为表锁。

就锁的应用场景而言,在回放场景中,如果确定事务可并发,则可以考虑不加锁,加快回放速度。

锁只是并发控制的一种粒度,只是一个很小的部分:

从不同场景下是否需要控制并发,(已知无交集且有序的数据的变更,MySQL 的 MTS 相同前置事务的多事务并发回放)

并发控制的粒度,(锁是一种逻辑粒度,可能还存在物理层和其他逻辑粒度或方式)

相同粒度下的优化,(锁本身存在优化,如IX、IS类型的优化锁)

粒度加载的安全&性能(如获取行锁前,先获取页锁,页锁在执行获取行锁操作后即释放,无论是否获取成功)等多个层次去思考并发这玩意。

mysql 做回归模型_技术分享 | 我对 MySQL 隔离级别的剖析相关推荐

  1. MySQL mdl导入_技术分享 | 深入理解 MySQL MDL Lock

    作者:高鹏(网名八怪) 文章末尾有他著作的<深入理解 MySQL 主从原理 32 讲>,深入透彻理解 MySQL 主从,GTID 相关技术知识. 本文来源:转载自公众号-mysql cod ...

  2. mysql表大小限制_技术分享 | 在磁盘上查找 MySQL 表的大小

    作者:Peter Zaitsev 翻译:管长龙 我想知道 MySQL 表在磁盘上占用多少空间,但看起来很琐碎.不应该在 INFORMATION_SCHEMA.TABLES 中提供这些信息吗?没那么简单 ...

  3. mysql安全无密码登录_技术分享 | 安全地无密码登录 MySQL

    有人说最好的密码就是你不用记忆的.auth_socket 插件和 MariaDB 的 unix_socket 让这种想法在 MySQL 上变成可能. 这两个插件虽然不是新发布,但在 MariaDB 1 ...

  4. mysql innodb 缓存设置_数据库分享一: MySQL的Innodb缓存相关优化

    无论是对于哪一种数据库来说,缓存技术都是提高数据库性能的关键技术,物理磁盘的访问速度永 远都会与内存的访问速度永远都不是一个数量级的.通过缓存技术无论是在读还是写方面都可以大大提 高数据库整体性能. ...

  5. mysql 做回归模型_GitHub - themycode/intelligent-test-platform: intelligent-test-platform

    概述 Markov(阿里妈妈功能测试平台)是在测试转型大背景下自研的新一代功能测试平台,相较于传统的功能测试框架具有着诸多的优点,比如可视化用例编写管理.分布式的沙盒环境和测试数据构建.测试流程pip ...

  6. mysql 创建索引 终止_技术分享 | 常见索引问题处理

    作者:EneTakane 数据库技术爱好者,爱可生 DBA 团队成员,负责 MySQL 日常问题处理以及数据库运维平台的问题排查,擅长 MySQL 主从复制及优化,喜欢钻研技术问题,还有不得不提的 w ...

  7. mysql内连接简写_技术分享 | MySQL 的 join_buffer_size 在内连接上的应用

    本文详细介绍了 MySQL 参数 join_buffer_size 在 INNER JOIN 场景的使用,OUTER JOIN 不包含.在讨论这个 BUFFER 之前,我们先了解下 MySQL 的 I ...

  8. mysql分布式主键_技术分享 | 优化 InnoDB 的主键

    作者:Yves Trudeau 翻译:管长龙 前言 作为 Percona 的首席架构师,我的主要职责之一是对客户的数据库进行性能方面的优化,这使得工作复杂且非常有趣.在这篇文章中,我想讨论一个最重要的 ...

  9. mysql 行锁 超时_技术分享 | MySQL 行锁超时排查方法优化

    作者:xuty 本文来源:原创投稿 * 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源. 一.大纲 #### 20191219 10:10:10,234 | com.ali ...

最新文章

  1. deepnode处理过的图片_这款实用的图片软件,其功能相当于十几款图片处理软件的功能之和...
  2. c 语言乘法代码,C++实现大数乘法算法代码
  3. ITK:就地过滤图像
  4. c语言明解课后答案,明解C语言 中级篇 第一章答案
  5. Linux Mysql 安装方法
  6. mysql fio测试_Linux下 fio磁盘压测笔记
  7. 2020年中国互联网租车报告
  8. 线程:synchronized方法
  9. Gstreamer的一些基本概念与A/V同步分析
  10. QT表格QTableWidget在win10下纵横表头无分隔线的问题
  11. Excel 2016 对数据做线性回归分析步骤
  12. 实用工具系列 - Xshell安装下载与使用
  13. 酷我音乐盒html代码,酷我音乐盒2018
  14. Activity类的7个生命周期方法
  15. panabit策略路由
  16. 关于图像模式识别的几种分类方法概述
  17. 关于时间复杂度什么是时间复杂度
  18. 泰拉瑞亚pc版最新服务器,服务端工具 - Terraria1.4.0.5-RPG开服端v0.2 | Terraria(泰拉瑞亚)中文论坛...
  19. java对接中金支付接口
  20. ROS SMACH个人学习记录

热门文章

  1. 【软件项目管理】里程碑事件的定义
  2. 在wordpress中使用 markdown:wp-markdown插件的使用方法
  3. linux8如何开启多个桌面,CentOS8安装GNOME3桌面并设置开机启动图形界面
  4. 浅析MySQL中exists,in ,=的使用
  5. Confirm Hosts Registration with the server failed
  6. linux C 中的volatile使用【转】
  7. 如何加密 Ubuntu 安装后的主文件夹
  8. azure centos 7安装mariadb
  9. esxi5.1 添加vSwitch,转VM network to new vSwitch
  10. Ubuntu 创建启动器