关于mysql:mysql中的各种锁把我搞糊涂啦

2次阅读

共计 4434 个字符,预计需要花费 12 分钟才能阅读完成。

大家好,我是公众号:java 小杰要加油
明天来分享一个对于 mysql 的知识点——mysql 中的锁

  • 话不多说,间接开车

事务并发拜访状况

读 - 读 状况

  • 并发事务读取雷同的数据,并不会对数据造成影响,容许并发读

写 - 写 状况

  • 多事务并发写写时会产生 脏写 的状况,不过任何一个事务隔离级别都不容许此状况产生,通过 加锁 来杜绝脏写

脏写

  • 事务 T1 将数据改成了 A, 然而还 未提交 ,可此时事务 T2 又将数据改成了 B,笼罩了事务 T1 的更改,T1 更新失落,这种状况叫做 脏写

加锁

  • 例如,当初事务 T1,T2 对这条记录进行并发更改,方才说是隔离级别是通过加锁来杜绝此脏写的,流程如下


这个锁构造中有两个比拟要害的信息(其实还有很多信息,前面再聊)

  • trx 信息:示意这个锁构造是和哪个事务所关联的
  • is_waiting 信息:示意以后事务是否正在期待

Q:能形容一下两个事务并发批改同一条数据时,mysql 这个锁是怎么防止脏写的吗?

A:事务 T1 在更改这条数据前,就先内存中生成一把锁与此数据相关联(is_waiting 为 false, 代表没有期待),而后咔咔一顿操作更改数据,这个时候,事务 T2 来了,发现此记录曾经有一把锁与之相关联了(就是 T1 那一把锁),而后就开始期待(is_waiting 为 true 代表正在期待),事务 T1 更改完数据提交事务后,就会把此事务对应的所构造开释掉,而后检测一下还有没有与此记录相关联的锁,后果发现 T2 还在苦苦的期待,就把 T2 的锁构造的(is_waiting 为 false, 代表没有期待)而后把 T2 事务对应的线程唤醒,T2 获取锁胜利继续执行,总体流程如上。

读 - 写 / 写 - 读 状况

在读 - 写 / 写 - 读的状况下会呈现 脏读,不可反复读,幻读 的景象,不同的隔离级别能够防止不同的问题,具体相干内容能够看小杰的这篇文章 京东面试官问我:“聊聊 MySql 事务,MVCC?”

不过贴心的我还是列出来了 注:√代表可能产生,×代表不可能产生

隔离级别 脏读 不可反复读 幻读
读未提交(read uncommitted RU)
读提交(read committed RC) ×
可反复读(repeatable read RR) × ×
串行化(serializable) × × ×

然而 RR 在 某些水平 上防止了幻读的产生

怎么防止脏读、不可反复读、幻读这些景象呢?其实有两种计划

  • 计划一:读操作 应用 MVCC 写操作 进行 加锁
  • mvcc 外面最重要的莫过于 ReadView 了,它的存在保障了事务不能够读取到未提交的事务所作的更改,防止了脏读。
  • 在 RC 隔离级别下,每次 select 读操作都会生成 ReadView
  • 在 RR 隔离级别下,只有第一次 select 读操作才会生成 ReadView,之后的 select 读操作都复用这一个 ReadView
  • 计划二:读写操作都用加锁

某些业务场景不容许读取旧记录的值,每次读取都要读取最新的值。
例如银行取款事务中,先把余额读取进去,再对余额进行操作。当这个事务在读取余额时,不容许其余事务对此余额进行拜访读取,直到取款事务完结后才能够拜访余额。所以在读数据的时候也要加锁

锁分类

当应用 读写都加锁 这个计划来防止并发事务 写 - 写 读 - 写 写 - 读 时而产生的 脏读 不可反复读 幻读 景象时,那么这个锁它就要做到,读读时不相互影响,下面三种状况时要互相阻塞,这时锁也分了好几类,咱们持续往下看

锁定读

  • 共享锁(Shared Lock):简称 S 锁,在事务 要读取 一条记录时,须要先获取该记录的 S 锁
  • 独占锁(Exclusive Lock):简称 X 锁,也称排他锁,在事务 要改变 一条记录时,须要先获取该记录的 X 锁

他们之间兼容关系如下 √代表能够兼容,×代表不可兼容

兼容性 S 锁 X 锁
S 锁 ×
X 锁 × ×

事务 T1 获取某记录的 S 锁 后,

  • 事务 T2也能够 获取此记录的 S 锁,( 兼容)
  • 事务 T2不能够 获取此记录的 X 锁,直到 T1 提交后将 S 锁 开释 (不兼容)

事务 T1 获取某记录的 X 锁 后,

  • 事务 T2不能够 获取此记录的 S 锁,直到 T1 提交后将 X 锁 开释 (不兼容)
  • 事务 T2不能够 获取此记录的 X 锁,直到 T1 提交后将 X 锁 开释 (不兼容)

锁定读语句

SELECT .. LOCK IN SHARE MODE   # 对读取的记录增加 S 锁

SELECT .. FOR UPDATE # 对读取的记录增加 X 锁

多粒度锁

后面提到的锁都是针对记录的,其实一个事务也能够在表级进行加锁(S 锁、X 锁)

  • T1 给表加了 S 锁,那么

    • T2能够 持续获取此表的 S 锁
    • T2能够 持续获取此表中的某些记录的 S 锁
    • T2不能够 持续获取此表的 X 锁
    • T2不能够 持续获取此表中的某些记录的 X 锁
  • T1 给表加了 X 锁,那么

    • T2不能够 持续获取此表的 S 锁
    • T2不能够 持续获取此表中的某些记录的 S 锁
    • T2不能够 持续获取此表的 X 锁
    • T2不能够 持续获取此表中的某些记录的 X 锁

可是怎么可能平白无故的就给表加锁呢,难道没什么条件吗?答案是必定有条件的

  • 若想 给表加 S 锁 ,得先确保 表中记录没有 X 锁
  • 若想 给表加 X 锁 ,得先确保 表中记录没有 X 锁和 S 锁

然而这个怎么确保呢?难道要一行一行的遍历表中的所有数据吗?当然不是啦,聪慧的大佬们想出了上面这两把锁

  • 动向共享锁(Intention Shared Lock): 简称IS 锁,当事务筹备在某记录上加 S 锁时,须要先在表级别加上一个 IS 锁
  • 动向独占锁(Intention Exclusive Lock): 简称IX 锁,当事务筹备在某记录上加 X 锁时,须要先在表级别加上一个 IX 锁

让咱们来看下加上这两把锁之后的成果是什么样子的

  • 当想给记录加 S 锁时,先给表加一个 IS 锁,而后再给记录加 S 锁

  • 当想给记录加 X 锁时,先给表加 IX 锁,而后再给记录加 X 锁

而后 通过下面的操作之后

  • 如果想给表加 S 锁,先看下表加没加 IX 锁,如果有的话,则表明此表中的记录有 X 锁,则须要等到 IX 锁开释掉后才能够加 S 锁

  • 如果想给表加 X 锁,先看下表加没加 IS 锁或者 IX 锁,如果有的话,则表明此表中的记录有 S 锁或者 X 锁,则须要等到 IS 锁或者 IX 锁开释掉后才能够加 X 锁

这几种锁的兼容性如下表

兼容性 IS 锁(表级锁) S 锁 IX 锁(表级锁) X 锁
IS 锁(表级锁) ×
S 锁 × ×
IX 锁(表级锁) × ×
X 锁 × × × ×
  • IS、IX 锁都是表级锁,他们能够共存。
  • 他们的提出仅仅是为了在之后加表级别的 S 锁或者 X 锁时能够疾速判断表中的记录是否被上锁,防止用遍历的形式来查看一行一行的去查看而已

InnoDB 中的行级锁

Record Lock(记录锁)

  • 官网名字 LOCK_REC_NOT_GAP
  • 仅仅锁住一条记录
  • 有 S 型和 X 型之分

Gap Lock(间隙锁)

  • 官网名字 LOCK_GAP
  • 给某记录加此锁后,阻塞数据在此记录和上一个记录的间隙插入,然而不锁定此记录
  • 有 S 型和 X 型之分,可是并没有什么区别他们的作用是雷同的,gap 锁的作用仅仅是为了避免插入幻影记录而已,如果对一条记录加了 gap 锁(无论 S / X 型)并不会限度其余事务对这条记录加 Record Lock 或者 Gap Lock

Next-Key Lock(记录锁 + 间隙锁)

  • 官网名字 LOCK_ORDINARY
  • 既能够锁住某条记录,又能够组织其余事务在该记录背后插入新记录

Insert Intention Lock(插入意向锁锁)

  • 官网名字 LOCK_INSERT_INTENTION
  • 事务在插入记录时,如果插入的中央加了 gap 锁,那么此事务须要期待,此时此事务在期待时也须要生成一个锁构造,就是插入意向锁

锁内存构造

  • 咱们难道 锁一条记录 就要 生成一个锁 构造吗?

当然不是!

一个锁构造

如果被加锁的记录合乎上面四条状态的话,那么这些记录的锁则会合到 一个锁构造

  • 在同一个事务中进行加锁操作
  • 被加锁的记录在同一个页面中
  • 加锁的类型是一样的
  • 期待的状态是一样的

锁构造信息

而后咱们再来依此看下这个所构造每个局部的信息都是什么意思

  • 锁所在的事务信息:无论是表级锁还是行级锁,一个锁属于一个事务,这里记录着该锁对应的信息
  • 索引信息:对于行级锁来说,须要记录一下加锁的记录属于哪个索引
  • 表锁 / 行锁信息:行级锁

    • Space_ID:记录所在的表空间

    * Page Number:记录所在的页号

    • n_bits:一条记录对应着一个比特;一个页面蕴含多条记录,用不同的比特来辨别到底是那一条记录加了锁, 有个计算公式如下(公式中是取商)n_bits =(1+(n_recs+LOCK_PAGE_BITMAP_MARGIN)/ 8)x 8LOCK_PAGE_BITMAP_MARGIN 是固定的值为 64,n_recs 指以后界面一共有多少条记录(蕴含伪记录以及在垃圾链表中的记录),
  • type_mode:32 比特的数

    • lock_mode(锁模式):低 4 比特位示意

      • LOCK_AUTO_INC(十进制的 4):示意 AUTO-INC 锁
      • LOCK_IS(十进制的 0):示意共享意向锁,IS 锁
      • LOCK_IX(十进制的 1):示意独占意向锁,IX 锁
      • LOCK_S(十进制的 2):示意共享锁,也就是 S 锁
      • LOCK_X(十进制的 3):示意独占锁,也就是 X 锁
    • lock_type(锁类型):第 5~8 比特位示意

      • LOCK_TABLE(十进制的 1):当第 5 比特位设置为 1 时,示意表级锁
      • LOCK_REC(十进制的 32):当第 6 比特位设置为 1 时,示意行级锁
    • rec_lock_type(行锁的具体类型):其余的比特位示意

      • LOCK_ORDINARY(十进制的 0):示意 next-key 锁
      • LOCK_GAP(十进制的 512):当第 10 比特位是 1 时,示意 gap 锁
      • LOCK_REC_NOT_GAP(十进制的 1024):也就是当第 11 比特设置为 1 时,示意 Record Lock(记录锁)
      • LOCK_INSERT_INTENTION(十进制的 2048):也就是当第 12 比特设置为 1 时,示意 Insert Intention Lock(插入意向锁)
      • LOCK_WAIT(十进制的 256):也就是当

        • 第 9 比特设置为 1 时,示意 is_waiting 为 true, 即以后事务获取锁失败,处于期待状态
        • 第 9 比特设置为 0 时,示意 is_waiting 为 false, 即以后事务获取锁胜利
    • 其余信息:此文章不探讨
    • 一堆比特位:此文章不探讨

    举个例子

    事务 T1 要给 user 表中的记录加锁,假如这些记录存储在 表空间号 为 20,页号 为 21 的页面上,T1 给 id= 1 的记录加 S 型 Record Lock 锁,如果以后页面一共有 5 条记录(3 条用户记录和 2 条伪记录)

    过程:先给表加 IS 锁,不过咱们当初不关怀,只关怀 行级锁
    具体生成的所构造如下图所示

    最初

    • 快过年啦,小杰可能也须要劳动一下下,因为最近都周更(尽管上周有点事没更,打脸),周末齐全没有其余工夫了
    • 感觉和朋友家人们分割有点少了,过年回家坚固下感情和敌人们聊聊天吹吹牛逼,顺便保护下峡谷的治安
    • 最初祝关注 java 小杰要加油 的宝贝儿们
    • 脱单暴富事事顺,升职加薪牛哄哄!

    好文举荐

    • 五千来字小作文,是的,咱们是有个 HTTP。
    • 同学,二叉树的各种遍历形式,我都帮你总结了,附有队列堆栈图解(坚固根底,强烈建议珍藏)
    • 京东面试官问我:“聊聊 MySql 事务,MVCC?(好文,倡议珍藏)”
    • 你好,我叫 AQS(系列一:加锁)
    • 京东这道面试题你会吗?
    • ?线程池为什么能够复用,我是蒙圈了。。。

    正文完
     0