mysql怎么加间隙锁 mysql间隙锁解决幻读

解决一次mysql死锁问题 多线程开启事务处理 。每个事务有多个update操作和一个insert操作(都在同一张表) 。
默认隔离级别:Repeatable Read
只有hotel_id=2和hotel_id=11111的数据
逻辑删除原有数据
插入新的数据
根据现有数据情况,update的时候没有数据被更新
报了非常多一样的错
发现居然有死锁 。
根据常识考虑,我每个线程(事务)更新的数据都不冲突,为什么会产生死锁?
带着这个问题 , 打印mysql最近一次的死锁信息
show engine innodb status
显示如下
发现事务1在等待一个锁
事务2也在等待一个锁
而且事物2持有了事物1需要的锁
关于锁的描述,出现了 lock_mode , gap before rec , insert intention 等字眼,看不懂说明了什么?说明我关于mysql的锁相关的知识储备还不够 。那就开始调查mysql的锁相关知识 。
通过搜索引擎 ,
锁的持有兼容程度如下表
那么再回到死锁日志,可以知道 :
事务1正在获取插入意向锁
事务2正在获取插入意向锁,持有排他gap锁
再看我们上面的锁兼容表格,可以知道,gap lock和insert intention lock是不兼容的
那么就可以推断出: 事务1持有gap lock , 等待事务2的insert intention lock释放;事务2持有gap lock,等待事务1的insert intention lock释放,从而导致死锁 。
那么新的问题就来了,事务1的intention lock 为什么会和事务2的gap lock 有交集,或者说,事务1要插入的数据的位置为什么会被事务2给锁?。?
让我回顾一下gap lock的定义:
间隙锁,锁定一个范围 , 但不包括记录本身 。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况
那为什么是gap lock , gap lock到底是基于什么逻辑锁的记录?发现自己相关的知识储备还不够 。那就开始调查 。
调查后发现,当当前索引是一个 普通索引 的时候,会加一个gap lock来防止幻读 , 此gap lock 会锁住一个左开右闭的区间 。假设索引为xx_idx(xx_id),数据分布为1,4,6,8,12,当更新xx_id=9的时候,这个时候gap lock的锁定记录区间就是(8,12],也就是锁住了xxid in (9,10,11,12)的数据 , 当有其他事务要插入xxid in (9,10,11,12)的数据时,就会处于等待获取锁的状态 。
ps:当前索引不是普通索引,而且是唯一索引等其他情况,请参考下面资料
MySQL 加锁处理分析
回到我自己的案例中,重新屡一下事务1的执行过程:
因为普通索引
KEY hotel_date_idx ( hotel_id , rate_date )
的关系 这段sql会获取一个gap lock,范围(2,11111]
这段sql会获取一个insert intention lock (waiting)
再看事务2的执行过程
因为普通索引
KEY hotel_date_idx ( hotel_id , rate_date )
的关系 这段sql也会获取一个gap lock , 范围也是(2,11111](根据前面的知识 , gap lock之间会互相兼容,可以一起持有锁的)
这段sql也会获取一个insert intention lock (waiting)
看到这里,基本也就破案了 。因为普通索引的关系 , 事务1和事务2的gap lock的覆盖范围太广,导致其他事务无法插入数据 。
重新梳理一下:
所以从结果来看 , 一堆事务被回滚,只有10007数据被更新成功
gap lock 导致了并发处理的死锁
在mysql默认的事务隔离级别(repeatable read)下 , 无法避免这种情况 。只能把并发处理改成同步处理 。或者从业务层面做处理 。
共享锁、排他锁、意向共享、意向排他
record lock、gap lock、next key lock、insert intention lock
show engine innodb status
什么是间隙锁?到底锁了什么?当前表中的数据为:
分别执行 session B中的insert 会出现上述情况mysql怎么加间隙锁,为什么mysql怎么加间隙锁?
这个问题其实是因为没有理解索引的结构,所以认为所有值都不应该被锁
问题一的部分来自 MySQL 中关于gap lock / next-key lock 的一个问题
问题二部分来自 什么是间隙锁?到底锁mysql怎么加间隙锁了什么?
当前表中的数据为:
分别执行 session B中的 UPDATE 会出现上述情况 , 为什么?
建表后,b字段上的2,4,6,8是以B tree的数据结构出现,为了方便理解,这里我们不强调B tree的结构,强调有序 。索引b上的数据结图如下所示:
锁住的部分应该如下图所示
本文是作者根据日常业务场景 , 写出的一些解决问题或实施想法的历程 。如有错误的地方,还请指出,相互学习,共同进步 。
MySQL存储引擎InnoDB的数据文件本身就是主索引文件 。而MyISAM的主索引和数据是分开的 。辅助索引data域存储相应记录主键的值而不是地址 。
innoDB是聚簇索引,数据挂在逐渐索引之下 。
是 MySQL 默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其它存储引擎。
实现了四个标准的隔离级别,默认级别是可重复读(REPEATABLE READ) 。在可重复读隔离级别下,通过多版本并发控制(MVCC)间隙锁(Next-Key Locking)防止幻影读 。
主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升 。
【mysql怎么加间隙锁 mysql间隙锁解决幻读】内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等 。
支持真正的在线热备份 。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取 。
以B 树作为索引结构,叶节点的数据域存放数据记录的地址 。主索引和辅助索引在结构上没有区别,只是主索引要求key唯一,而辅助索引的key可以重复 。
MyISAM中索引检索的算法为首先按照B Tree搜索算法搜索索引 , 如果指定的Key存在,则取出其data域的值,然后以data域的值为地址 , 读取相应数据记录 。
设计简单,数据以紧密格式存储 。对于只读数据,或者表比较小、可以容忍修复的操作,则依然可以使用它 。
提供了大量的特性,包括压缩表、空间数据索引等 。
不支持事务。
不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁 。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT) 。
可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失 , 而且修复操作是非常慢的 。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘 , 而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘 。这种方式可以极大地提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏 , 需要执行修复操作 。
记录锁、间隙锁、临键锁 这三种并不是锁,而是锁的算法 。它们的共同特点是互斥的 。
间隙锁和临键锁只有在RR级别中才能生效 。
间隙锁的目的是为了防止多个事务把记录插入到同一范围中去,这样能防止幻读
间隙锁可能会出现在唯一索引和辅助索引,现在分情况讨论 。
参考: MySQL的锁机制 - 记录锁、间隙锁、临键锁 , 《MySQL技术内幕》
关于mysql怎么加间隙锁和mysql间隙锁解决幻读的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站 。

    推荐阅读