1. 如何设计一个关系型数据库
————存储管理【数据逻辑-物理逻辑管理】————缓存模块————SQL解析模块————权限划分————索引————锁
2. order by排序怎么理解?
————当我们使用order by将查询结果按照某个字段排序时,如果该字段没有建立索引,那么执行计划会将查询出的所有数据使用外部排序(将数据从硬盘分批读取到内存使用内部排序,最后合并排序结果),这个操作是很影响性能的
3. 联合索引、索引覆盖?也是全覆盖索引
————索引:加快查询,避免进行全表扫描 目前绝大多数情况下使用的数据库索引都是使用B+树实现的
————联合索引:INDEX idx_test(col_a, col_b) 包含多个字段的索引 险 = 耳朵 + (6-8画)
————最左前缀匹配:如果查询条件为where col_b = 1,则无法使用索引idx_test
————如果要查询的字段有索引,那么引擎会直接在索引表中查询而不会访问原始数据(否则只要有一个字段没有建立索引就会做全表扫描),也就是说:不需要回表
————那么如果我们只是想要验证险字的偏旁是否是双耳旁“阝”呢?这种情况下,我们只要在部首索引中阝下游的叶子节点中找到了险字就足够了。这种在索引中就获取到了SQL语句中需要的所有字段,所以不需要再回表查询的情况中,这个索引就被称为这个SQL语句的全覆盖索引
————在现代的操作系统中,把数据从外存读到内存所使用的单位一般被称为“页”,每次读取数据都需要读入整数个的“页”
————varchar(50) 最多存放50个字符 (50)和(200)存储hello所占用的空间一样,但是后者在排序的时候,会消耗更多的内存
4. 百万级别数据如何删除?
————索引维护【innodb说是单独文件,修改删除都会对索引操作,需要多次io】1. 删除索引 2. 删除数据 3. 重新创建索引
5. 聚集索引/非聚集索引?
————聚集索引:将数据存储与索引放到了一块,找到索引也就找到了数据。 在MySQL数据库的InnoDB存储引擎中,主键索引就是聚集索引,所有数据都会按照主键索引进行组织 新华字典的拼音索引就是聚集索引
————非聚集索引:某种情况下需要回表
6. 索引根据添加分为?
————1. 主键索引 2. 唯一索引 3. 普通索引
7. 如何分析慢查询问题?
————1. 开启慢查询 2. explain 3. 修改sql添加索引
8. myisam为什么count快?
————因为无需计算,直接返回 ——存储特性问题 —— 没有where条件的场景下才有意义
————myisam没有事务,时候频繁count的场景
9. 为什么特殊情况下select count 使用的不是主键索引?
————非主键索引存储的是对应列值和主键值
————主键索引存储的是对应列值和数据
10. 锁?
————myisam表级别,innodb行级和表级别
————myisam select添加共享锁,增删改 写锁。 读锁后,允许读/不允许写
————myisam select for update添加拍他锁
————innodb使用的是二段锁【加锁、解锁是两个步骤】commit的时候会解锁
————autocommit=0关闭自动提交或者begin transaction相当于需要commit提交
————select lock in share mode添加共享锁,然后同一条语句update的时候就会阻塞。update不同的数据发现是可以的【默认行级锁】
————非索引字段添加共享锁,会被锁表————悲观锁:数据库的锁机制【排他锁】
————乐观锁:版本号11. 事务?
————原子性、一致性、隔离性、持久性
————脏读【a事务读取到b事务还没有提交的数据】:READ-UNCOMMITTED下会有这种情况 解决方案是:调整为READ-COMMITTED
————不可重复读【a事务多次读取同一数据,b事务在a读取过程中修改并提交数据,数据不一致】:RC下会出现 RR下可以避免 repeatedtable read
————幻读【a事务查询若干条数据,b事务插入或删除修改a事务的结果集,a会出现幻觉】:innodb下 RR下会避免幻读,RC下会出现【最高级别可以避免】
————如何真正避免幻读呢?
————————next-key锁【record+gap lock(ru和rc下没有)】
————————仅命中条件的部分结果集并且用于主键索引、唯一索引的当前读中
————————非唯一索引或者不走索引的当前读中,非唯一索引6,9,9,11,gap是(负无穷,6],(6,9],(9,11],(11, 正无穷) ,如果where id=9,那么会锁住(6,11]。如果不走索引,会对所有的gap都上锁
————最高隔离级别Seriariable下因为是串行的,所以所有的sql都相当于加上了锁
————当前读和快照读【非阻塞读】
——————快照读:非最高隔离级别的select【有可能读取到不是最新的数据,是历史版本】 —————DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID,当我们执行变更的时候就会有undo日志,roll_ptr指向了旧版
——————当前读:select for update,select lock in share mode,update,delete,insert
innodb rr下竟然避免了幻读?
如何设计一个关系型数据库?
数据库最主要的功能是存储数据,因此有一个存储模块存储数据。存储模块类似OS文件系统,将数据持久化存入磁盘中,如存入机械硬盘、SSD固态硬盘、亦或者是它们的磁盘阵列矩阵中。
但是只有存储是不行的,还需要组织并且用到这些数据,因此需要有程序的实例,用逻辑结构来映射出物理结构,并且在程序中提供获取和管理数据的方式,以及提供必要的问题追踪机制。
细分程序模块:
1)数据逻辑关系转换成物理存储关系的存储管理模块:首先对数据的格式和文件的分隔进行统一的管理,即把物理数据通过逻辑的形式组织表示出来,便涉及到程序的存储管理模块。(优化存储效能:处理数据不在磁盘上做,而是加载到程序空间所在内存里,磁盘IO速率是程序执行速率的主要瓶颈,远差于内存的执行效率。为了执行效率,要尽可能减少IO。就存储管理而言,如果按照逐行查找并返回,频繁的IO会使数据库的执行效率慢。因为一次IO读取单条数据和多条数据没有太大的区别,所以可以一次性的读取多行,以提升IO的效能。行就失去了意义,数据以块和页作为逻辑存储单位,每个块和页中存放多行数据,读取的时候将多个块和页加载进内存中。)
2)优化执行效率的缓存模块:为了更快更好的优化利用内存,可以利用缓存机制,把取出来的数据块放进缓存里,下次需要的时候直接从内存返回,而不用发生IO。一次性加载多个模块或者页,块里包含的数据行有数据可能不是我们本次查询需要的行,但是一旦某行数据被访问了,它周围的数据也极有可能被访问的经验,缓存的非本质数据也能起到优化访问效率的作用,提升访问的性能。管理缓存的方法有LRU等。
3)将SQL语句解析的SQL解析模块:提供外部指令操纵数据,即可读的SQL语言,需要SQL解析模块将SQL编译解析,转换成机器可识别的指令。这时为了进一步提升SQL的执行效率,将SQL缓存到缓存里直接解析。缓存不宜过大,且有算法里淘汰机制,淘汰掉之后不常用的数据。
4)记录操作的日志管理模块:SQL操作需要记录下来,方便数据库的主从同步或者灾难恢复,因此需要日志管理对操作进行记录,如binlog的记录方式。
5)多用户管理的权限划分模块:还需要提供给用户管理数据的私密空间,即权限划分。通俗将就是老板可以看到员工的数据,员工只能看到自己该看到的数据。权限划分是DBA做的。
6)灾难恢复模块:除了考虑正常情况,还需考虑异常情况,需要引入异常机制,即容灾机制。当数据库挂了如何恢复,恢复到什么程度。
7)优化数据查询效率的索引模块和使得数据库支持并发操作的锁模块:为了进一步提升查询数据的速度以及让数据库支持并发,需要引入索引和锁模块。
索引模块
一、常见问题
-
为什么要使用索引?
快速查询数据【灵感来源于:字典,避免进行全表查询】
一个索引是存储的表中一个特定列的值数据结构(最常见的是B-Tree,B-Tree是有序的)。索引是在表的列上创建。所以,要记住的关键点是索引包含一个表中列的值,并且这些值存储在一个数据结构中。请记住记住这一点:索引是一种数据结构 。
哈希索引:
哈希是无序的,但是效率极高,所以通常不会作为数据库索引的默认结构,不够灵活
数据库索引——存储了对应列的值,还存储了一个指向行数据的索引
本原则是如果表中某列在查询过程中使用的非常频繁,那就在该列上创建索引。
-
索引的分类?——数据库索引是什么?新华字典来帮你 - 掘金
主键索引(列值唯一,表中只有一个)、唯一索引(列值唯一)、普通索引、全文索引、联合索引
联合索引:字典中先根据偏旁部首找到“耳”,然后根据右边笔画(6-8画),找到“险”
——
——
聚集索引:我们在新华字典的侧面可以看到一个V字形的一个个黑色小方块,有很多人都会在侧面写上
A, B, C, D
这样对应的拼音字母。因为字典中所有的字都是按照拼音顺序排列的,有时候直接使用首字母翻开对应的部分查也很快。
非聚集索引:
全覆盖索引:
这种方式:是索引本质上使用B+树的核心原因
【注:上述会让时间复杂度从Logn到N,即使通过二叉树的旋转变成了平衡二叉树,那么也会有很多层,会产生很多次的io】,所以就需要树的高度矮一些。每个节点存储的内容多一些
慢查询:
慢查询日志打开
设置慢日志查询的超时时间
慢日志内容:
这里统计id。使用的是account索引,并没有使用主键索引
密集索引:
叶子节点保存的不仅仅是键值,还有数据记录,位于同一hang记录的其他信息
person是innodb【frm是结构,idb是数据和索引】
shop是myIsm【frm是结构,myi是索引,myd是数据】
数值类型:
myisam引擎下:如果查询先执行,没有执行完成的情况下,更新语句来了,会发现更新语句明显变慢了,update要等待query完成之后,才开始执行
也就意味着,当表进行查询的时候,myisam会自动给表上一个表锁。会锁住这张表。会block其他的session对其数据的更新。
当select时候,会自动加上表级读锁。当操作增删改的时候会加上表级写锁。如果读锁没有释放,其他session要加上写锁的时候就会被阻塞。。。。直到所有的读锁被释放为止
读锁也叫做共享锁。
当第一条没有结束的时候,不影响第二条select查询
如果先进行写锁,然后进行读/写锁,就会被阻塞。需要等待写锁的释放
写锁:也称为排他锁。
这里的实验结果是:给select语句上排他锁。
接下来:这里看看Innodb的情况呢????
会发现两条语句都是无须等待。本质上innodb用的是二段锁。【加锁、解锁是分成两个步骤的】先对用一个事务里的一批操作进行加锁,commit的时候,在对事务加上的锁进行解锁,而当前的commit是自动提交的,所以看起来和myisam没有多大区别
请注意:该设置仅仅作用当前session...。。。
如果不想额外关闭自动提交,可以采用这种方式,同样可以达到效果
加共享锁
另外一个session执行update,发现居然可以执行成功???
因为第一条的commit还没有提交。共享写锁还没有释放。完全颠覆了认知。【共享锁没有释放,理论无法加入排他写锁】
本质的原因是:innodb对select进行了改进,select并没有对该行上锁。所以才更新成功了
先提交下,再考虑给后续的情况
显示上锁【共享锁】
接下来在执行update。会发现被block了
需要我们在加读锁的session里面进行commit。commit之后,update语句才可能成功
给id=3加上共享锁。
修改id=4的情况 。会发现当前这个语句并没有被block。
证明innodb是默认支持行级别的锁。
验证下:共享锁没有释放的情况下,是否可以继续加入共享锁
一个session给id=3加入共享锁
另一个session同样给id=3加入共享锁。发现是同样可以读出数据的
用到表级锁的时候,只要操作表里的数据,都会上表锁,因此表级锁跟索引无关。
行级别的锁是否和索引有关系?
除了id主键索引以外的其他索引,都会被上共享锁或者排他锁
不走索引的情况会发生什么?
给一个不是索引的字段加上共享锁
另一个session进行更新。因为是不同数据行数据,所以应该不会被block才对。但是被block了,需要等待第一个select进行解锁后,才能去更新。。。
也就是说:当不走索引的时候,整张表就会被锁住,此时的查询用的是表级别的锁。innodb
当sql用到了索引的时候是行级锁
INnodb除了支持 行级锁,也支持表级意向锁。【也分为共享读锁IS、排他写锁IX】主要是为了进行表级别的操作的时候,不用额外确认每一行是不是有行锁。
select for update。lock for mode就是显示锁
对数据进行操作上的锁就是DML锁,对表结构进行的变更就是DDL锁
悲观锁:正如其名(对数据被外界,本系统当前的其他事务,以及外部系统的事务处理) 对外界的修改持保守态度,因此在数据处理中将数据处于锁定状态,悲观锁的实现往往依靠数据库提供的锁机制【也只有数据库提供的锁机制可以保证数据的排他性】,否则即使再系统中实现了加锁机制,也无法保证外部系统不会修改数据。全程用排他锁锁定。
悲观并发控制本质是:先取锁再访问的保守策略,为数据安全提供了保证,但是再效率方面,处理加锁的机制,会让数据库产生额外的开销,同时增加产生死锁的机会
乐观锁:认为数据一般不会造成冲突,只有在数据正式提交更新的时候才会对数据的冲突与否进行检测,如果发现冲突则返回冲突信息,让用户自己决策
乐观锁不会使用数据库的锁机制,一般实现方式是记录version
其中上面提到的多session之间的互锁操作就是悲观锁,接下来看看verison实现的乐观锁
数据每更新一次就对version进行+1,当准备更新的时候,使用第一次取出来的verion,进行比对。如果一致,那么更新,否则认为是过期数据
为了模拟这种情况。首先需要满足自动提交
程序一:
程序二:也发现结果是0
程序二先去执行:
程序一执行:
有冲突。交由程序自己处理
原子性:事务包含的所有操作,要么全执行,要么全不执行
一致性:数据库应该从一个一致状态转变为另外一个一致的状态【数据满足完整性约束,如:转账,用户A和用户B进行转账,那么总金额不会变 】
隔离性:多个事务并发执行,一个事务的执行不应该影响另一个事务
持久性:一个事务一旦提交,那么对于数据库的修改应该永久保存在数据库中,
现在主流数据库都会为我们加锁来避免更新丢失的情况。所以在数据库层面不好模拟
查看事务的隔离级别【默认是REPEATABLE-READ】
脏读:是一个事务读取到另一个事务还没有提交的数据
设置隔离级别为“读未提交”
查询已经生效【两个session都是最低的隔离级别】
session1更新后,未提交事务。通过查询发现余额变成了900
session2进行查询,竟然发现这个余额变成了900,此刻它就真的以为是900的数据了
【如上:就是所谓的脏读了】
但是session1此时不提交,而是回滚了
回滚之后,数字就又变成了1000.
此刻session2对数字+200.同时也提交了事务,同时也发现结果是1100
注意:虽然这里看不出什么问题。但是对于拥有上帝视角的我们来说,问题就很大了【莫名其妙】
解决方案是:将隔离级别设置成
设置完成后查询下:
同样的:
session1:先更新后读取,数据是1000.
session2去查询,发现账户余额是1100。我们发现此刻已经不是之前同样情况下读取的未提交事务的结果值了
事务a多次读取同一数据,事务b 在事务a多次读取过程中做了更新并提交。导致事务a多次读取结果时候,数据不一致。
session1:在一定时间里面多次读取。发现读取的结果有可能不一样
第一次是1300【session2⃣️还没有提交的情况下 】,第二次是1600【对于上帝视角的我们是正常的。但是对于sesision1来考虑下?】
session2:【查询结果是1600.】
如上:就是不可重复读产生的问题。。。。。
解决不可重复读的问题的解决方案就是:将隔离级别调大,调整为innodb的默认隔离级别。
设置完成后查询下:【发现已经被设置成了】
session1、2分别开启事务。。。
session1看到的结果是1600.。。
session2同样的读取到数据是1600
session2存储好了之后呢?开始进行查询。发现结果是2000
此时session2未提交,session1去查询账户【发现session1还是读取结果是1600,避免了脏读】
然后session2开始进行提交操作。。。
session1在进行一遍查询,发现结果是1600【确保了不管别的事务如何提交。自己用的那一份还是不变的。从上帝视角看,如果session1仍旧把1600当作余额,如:取出100变成1500,这样数据已经在并发访问下出问题了。那?能否避免这样的问题发生呢?】
回顾一下:先前的余额是1600.session2存入400变成了2000,此时再取100,那么1900就是正确的。如果是变成1500那么就会很荒谬了。。。
如何避免可重复读。只是一个体外话。 关键是证明了什么是不可重复读问题,以及证明了通过设置为当前隔离级别,可以避免此类问题的发生。
事务a读取与搜索条件相匹配的若干行。事务b以删除或者插入的方式来修改事务a的结果集。导致事务a出现看起来像是出现幻觉一样。
查看并确认当前的事务隔离级别。
值得注意是的:之前是对数据库的一行 做操作,现在我们对所有记录做操作
session1开启事务。
session2开启事务
在session1里面用当前读来获取整张表的数据。 【读取的是当前事务提交的最新的数据】当前读和快照读后续会说明。
在session2里面执行插入数据
预期的是session2执行更新并且提交后,session1紧接着进行一次更新将所有账户余额设置1000.【站在session1的角度,本来我是更新3条数据。结果竟然莫名其妙告诉我们更新了4条记录,这第四条记录就是所谓的幻行了】这种就是所谓的幻读了。。。
指的是在事务a执行了一个当前读操作
而另外一个事务b在事务a的影响区间内插入了一条数据
这时候,事务a再执行一个当前读操作,【update也是当前读操作】,出现了幻行,就好像发生了幻觉一样。当然了幻读也包括,另外一个事务对同一张表删除数据,导致当前事务的读操作比之前少了一行的情况。。。
真实验证下::::
session1里面对三条记录上锁。。。
session2对第四条记录进行插入操作。会发现block了
虽然innodb默认支持的是行级锁,理论上并不会锁入新插入的数据,然而实际上?
该行还是被锁住了。需要等待session1提交后才能插入。
此时对于session1来说:新增的数据并没有出现。innodb竟然在rr情况下避免了幻读的情况?
实际上,从理论来讲这种情况是避免不了幻读的,mysql innodb却做到了【好神奇啊】
我们再来复现下幻读的问题。
session2刚才等待超时了,我们直接rollback
session1也直接rollback。
将两个session的隔离级别都降低为RC
两个session都打开事务
session1使用当前读的方式来查询下。发现有三条记录
session2直接来插入第四条数据。与之前不同的是,竟然成功了
然后提交数据
然后session1对表里面所有数据进行更新1000.这里竟然发现更新了4条数据,本来是对三条数据进行更新,但是莫名其妙变成了4条。
这个时候,我们就会发现幻读情况出现了
我们session并不知道其他session在做插入操作
那么如何避免幻读呢?将数据隔离级别设置为最高的隔离级别?
session1、2都设置
将两个session都进行commit
分别查询下当前的事务隔离级别。。。
session1开启事务管理
session2开启事务管理
使用session1来进行查询,去掉了lock in share mode。【在最高的隔离级别下,所有的sql都会加上锁】
session2在往里面插入第5条数据,发现出现了block
会发现,在当前的隔离级别下,insert被block了。直到session1commit或者rollback才可以
这样就避免了幻读的发生啊
不可重复读:侧重对同一数据的修改
幻读:侧重于新增或者删除
那么是不是事务隔离级别越高越好呢?
主要是为了性能考虑,隔离级别越高,安全性越高,串行话越严重。具体依赖具体场景
当前读:便是加了锁的增删改查【不管是共享锁还是排他锁】,因为读取的是记录的最新版本。读取后,还需要保证其他并发事务不能修改当前记录【对读取的记录加锁】
除了上面罗列的第一条会加上共享锁,其他都会加上排他锁
那么?为什么update、delete、insert也是当前读呢?
如上:update内部就有一个current read【当前读】来获取数据的最新版本
同理delete操作也一样
insert会有些不同【可能会触发唯一键的冲突检测】
这里的前提是:隔离级别不能是最高。否则会退化成当前读
之所以出现快照读,是出于提升并发性能的考虑呢【基于多版本并发控制】MVCC。
MVCC可以理解为行级锁的一个变种,在很多情况下避免了加锁操作,开销低
说明:有可能读取到的不是数据的最新版本,而是历史版本
session1:隔离级别RC
session2:同样也是RC
session3是:RR
session4: RR
首先在RC下进行如下操作:
session1开启事务
session2开启事务
session1:开始查询id=2的数据
session2开始进行更新并进行提交操作,将1000更新为600
session1快照读变成了600
session1当前读发现也变成了600
在session1里面分别用当前读和快照读来进行查询
session1提交commit
session2提交commit
同样的:我们再来看看RR情况下的具体情况?
首先确认session3隔离级别是RR
session4同样是RR
session3开启事务
session4开启事务
session3查看当前账户余额是600
session4里面将其修改为300 。并进行提交
session3查看当前读。已经被更新成300了
session查看快照读,发现已然是没有修改数据
也就是说:RR隔离级别下。快照读有可能读取到数据的历史版本呢
那新的问题来了?能否在快照读里读取最新的数据?
提交刚才session3的commit
提交session4的commit
session3开启事务
session4开启事务
session4:更新并提交结果数据
session3查看当前读、快照读,发现都是0
也就是说:RR下首次调用快照读的地方很关键。也就是创建快照的时机决定了读取数据的版本【上面是update。delete、insert都是一样的】
非阻塞读就是快照读。。。
TRX:就是跟事务相关的,用来标示最近一次对本行记录进行修改【insert、update】的事务id
ROLL:回滚指针
ROW:行号
当我们对记录做了变更操作的时候,就会有undo日志,存储的是旧版的数据。当一个旧的事务需要读取数据时候就可以读取到旧版的数据了
简版演示【需要变更是rowid为1的,被事务a进行修改,将原来field2里面的值,从12-》32】
修改流程为:
1. 排他锁锁定该行。 将该行修改前的值copy一份到undo log里面2. 修改当前行的值
3. 填写事务id
4. 使用回滚指针指向undo log里面修改前的行
假设数据库,对应的undo log还没有被清除
此时某个事务又对该行进行修改【将field3从13-》45】,还是rowid=1的场景,那么undolog里面又回多一条记录。数据的多个版本就是这样实现的
主要用来做可见性判断的。当我们执行快照读select的时候,会针对我们查询的数据创建一个read view。来决定当前事务可以看到的是那个版本的数据
通过这两个值跟db-trx-id进行对比。确定是否从undolog里面取值
快照读并不能真正的解决RR避免幻读。有一种掩耳盗铃的意思
真正避免幻读的能力是:内在有next-key锁
行锁:是对单个记录行上的锁
Gap锁:间隙锁,锁定一个范围,但是不包括记录本身,为了防止同一事务的两次当前读,出现幻读的情况。在RU、RC隔离级别是没有的
意思是:精确查询的时候,所有记录都有
select * from XXX where id in (1,3,5); //如果id=1、3、5均在此table里面,并且出现就是全部命中,如果只查询到了1,3美元查找到5,那就是部分命中
如果其他事务新增数据,那么会在该数据之外,所以其他事务新增数据并提交之后,A事务再去进行当前读还是会获取原先的数据,并不会产生所谓的幻读现象,所以加行锁就足够了
我们有图示所对应的数据
此时,两个session的隔离级别均为RR
同时:对两个session开启事务管理
注意id是唯一索引,name是主键。上述delete走的是唯一索引【explain验证】
![]()
session1开始执行。此时如果有gac锁的话,9周围的间隙将会被锁起来【也就是说:如果另一个事务插入id=9的记录会被block住】
session1没有提交之前,我们试图给session2里面插入 数据【发现session2执行成功了】
也就是说:当前读走的是唯一索引。并且命中了数据的话 是不会加gap锁的
首先:
两个session都rollback回去
删除一个不存在的记录,使用session1删除7,session2插入8
两个session同时开启事务
session1删除7,删除一个不存在的值
session2插入一个不存在的8。我们发现虽然删除的是7,但是8却被block了,证明7周围的间隙也被锁住了
在来验证下,部分命中的情况
先将两个事务都进行回滚
![]()
此时:再次开启两个session的事务
session1查询部分存在的场景,7不存在
session2插入4,成功了
session2插入7,block了。这里说明【5-9】添加了gap锁
session2插入8,block了
session2插入10,发现没有被block
session1提交前,session2执行插入操作
所以:如果是部分命中的话,也会部分加上gap锁
在验证下全部命中的情况
两个session都回滚
两个session重新开启事务
session1执行精确查询,全部命中5、6、9
session2再次试试插入7成功
session2再次试试插入8成功
全部命中是不会加gap锁的
如上的GAP区间。
两个session都在RR下,同时开启事务
session1删除id=9的数据。【有两行】
session1提交前,session2企图给里面添加id=9的数据【block了】,需要等待session1提交或者回滚才可以。这样就避免了幻读现象的发生
GAP:(6-11】
session2尝试插入5,7,12
5——成功
7——block
12——成功
同时,该区间也和主键对应的大小相关
![]()
会发现插入bb的时候成功了,由于主键索引按照叶子结点首字母排列 。这行数据在6-11开外,所以不会锁住
插入dd的时候,会发现block了,首字母排列,会发现dd是位于6-11范围内的
会对所有的gap都上锁,同样可以达到幻读的效果‘
所有session开启事务
session1试图删除id=9的数据
session2插入2发现会被block
INnodb RR级别主要通过next-key锁来避免幻读问题。
next-key有gap lock和record lock
gap lock会用于 :
【非唯一索引或者不走索引的当前读中】【以及仅命中条件的部分结果集并且用于主键索引、唯一索引的当前读中】