• Mysql 锁


    系列文章目录和关于我

    参考书籍《mysql是怎样运行的》
    推荐极客时间《mysql实战45讲》
    

    一丶为什么数据库需要锁#

    数据库锁设计的初衷是处理并发问题。作为多用户共享 的资源,当出现并发访问的时候,数据库需要合理地控制资源的访问规则。而锁就是用来实 现这些访问规则的重要数据结构。

    根据加锁的范围,MySQL 里面的锁大致可以分成全局锁表级锁行锁三类

    二丶全局锁&全库逻辑备份#

    全局锁就是对整个数据库实例加锁。全局锁的典型使用场景是,做全库逻辑备份,全库逻辑备份有以下几种方式:

    1.Flush tables with read lock (FTWRL)#

    Flush tables with read lock (FTWRL)可以让整个库处于只读状态的时候,使用这个命令,之后其他线程的数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句讲被阻塞。之所以备份需要加全局锁是为了保证数据的一致性,

    2.mysqldump –single-transaction#

    使用此命令会先启动一个事务拿到一致性视图。由于 MVCC 的支持, 这个过程中其他线程可以进行正常操作,但是使用mysqldump –single-transaction的前提是需要支持事务,如果存在MyISAM引擎的表,并不能保证一致性。

    3.set global readonly=true#

    set global readonly=true也可以保证全库只读,主要有两个原因:

    • 在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大。
    • 在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开, 那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。

    三丶锁解决并发事务带来的问题#

    1.锁解决脏写#

    多个未提交事务修改相继对同一条记录进行改动的时候,需要进行排队,排队过程其实就是通过为记录加锁实现的。当一个事务想修改记录的时候,首先需要看下有没有于记录相关联的锁结构,如果没有,那么会在内存中生成与之对应的锁结构

    image-20221127205616517

    可能多个事务同时修改同一记录,会产生多个锁结构,其中只有一个事务可以获取到锁,其is_waiting为false,其他事务is_waiting为true,当获取锁的事务结束后,会唤醒其他等待的事务

    2.脏读,幻读,不可重复读如何避免#

    mysql innodb 在repeatable隔离级别下很大程度下避免了幻读(后续详细说到)

    2.1 mvcc解决脏读,幻读,不可重复读问题#

    Mysql InnoDB多版本并发控制MVCC 这篇博客我们详细说到了mvcc的实现的原理,简单来说就是在事务运行时会生成read view,其包含四个部分

    • m_ids:在生成read view时,当前系统中活跃的读写事务id列表
    • min_trx_id:生成read view时,当前系统中活跃的读写事务中最小事务id,也就是m_ids中的最小值
    • max_trx_id:生成read view时,系统应该分配给下一个事务的事务id值
    • creator_trx_id:生成该read view的事务的事务id

    image-20221113143118836

    查询语句只可以查询生成read view 时刻可以看到的数据,及事务id小于min_trx_id中的数据,写操作则针对最新的数据。非当前读的普通语句在读数据的时候,生成read view的时机不同,在可重复读的时候只会在第一次读取(如果使用start transaction with consistent snapshot则是在事务启动时)生成一个read view 后续不变,达到可重复读的目的,对于读已提交,则是每次读取都会生成新的read view,从刷新未提交事务集合,和min_trx_id,读取到已经提交的数据。使用mvcc让读写并不冲突,数据库的并发能力更强。

    2.2读写均加锁#

    比如在银行存款业务中,我们需要先读取账户余额,然后加上新增存款,然后进行写回操作,整个流程中,我们不希望存在另外一个ATM进行存取的操作,读写都需要排队,这时候就得使用锁。

    上面我们说了,写操作进行排队可以解决脏写,那么脏读,不可重复读,幻读是怎么使用读写加锁解决昵?

    脏读产生的原因是,当前事务读取到了另外一个事务没有提交的数据,那么只需要另外一个事务对操作的记录加锁,当前事务无法获取到锁,自然就不会发生脏读。

    不可重复读产生的原因是,当前事务先读取了一条记录,然后存在另外一个事务修改了此记录的数据,那么只要当前事务对记录进行加锁,自然后续的事务将无法修改,自然不会发生不可重复读。

    幻读产生的原因是,当前事务根据条件查询得到一批数据,然后后续事务新增了满足这些条件的数据,导致再次查询时发现多了一些数据,如同出现了幻觉。这里加锁则不是单对记录加锁,而是要锁住一个范围,让其他事务无法插入数据,从而解决幻读(后续会说到这种锁)

    很明显读写都加锁,并发能力不及mvcc

    3.一致性读#

    事务利用MVCC进行读取操作称为一致性读(又称一致性无锁读,快照读),基本上所有的普通读在可重复读,读提交隔离级别下,都是一致性读。

    4.锁定读#

    4.1共享锁&独占锁#

    共享锁:简称S锁,事务要读取一条记录的时候需要先获取到记录的共享锁

    独占锁:简称x锁,当事务需要改动记录的时候,需要先获取记录的独占锁

    共享锁和共享锁兼容,独占锁和独占锁,独占锁和共享锁互斥

    4.2锁定读的语句#

    select xxx lock in share mode可以对读取的记录加S锁

    select xxx for update可以对读取的记录加X锁

    4.3 写操作#

    • delete

      删除记录首先要在b+数中定位到这条记录的位置,然后获取x锁,然后指向delete mark(标记记录被删除)

    • update

      如果未修改键值,并且修改前后数据的存储空间大小不变,那么现在b+树上定位记录,然后加x锁。反之需要在b+树上定位数据,然后把记录彻底删除吗,然后再插入一条新的记录,对新增的这条数据加x锁

    • insert

      新插入记录一般都是加隐式锁(后面说)不需要在内存中生成对应的锁结构。

    三丶InnoDB表锁#

    1.表级S,X锁#

    innodb支持表级锁,也支持行级锁,表级锁粒度相对更粗,占用资源较少。使用表级锁效果相当于为表中的所有记录加锁,所以性能比较差。

    • 表级S锁,X锁

      使用Lock Tables t Read,innodb存储引擎会对表t加共享锁

      使用Lock tables t write,innodb存储引擎会对表t加独占锁

    类似于Java中的读写锁,共享锁和共享锁不互斥,独占锁和独占锁,独占锁和共享锁互斥。

    2.表级意向锁#

    innodb存储引擎中,当对表中某些记录加S锁之前,会在表上加上一个IS锁,同样加X锁之前会加表级IX锁,这里的I表示意向锁,SX表示共享还是互斥,表级意向锁存在的目的是后续对表加S锁,X锁的时候,可以快速判断表中是否存在加锁的记录,避免遍历每一个记录查看是否被加锁。

    image-20221127221414770

    3.表级AUTO-INC锁#

    mysql可以为某列执行Auto Increment自增,系统给自增列赋值的实现方式主要存在两种

    • 使用AUTO-INC锁,执行插入语句的时候加一个表级AUTO-INC锁,然后为每条待插入的记录中的自增列,进行递增赋值,单个插入语句执行结束后释放AUTO-INC锁。这样会导致其他事务的插入被阻塞。
    • 采用轻量级AUTO-INC锁,在为插入列赋值结束后,就释放轻量级锁,而不是插入语句执行完后才释放

    四.MDL#

    一般情况下 对某一个表执行增删改查的时候,都不会加表锁,但是执行一些DDL修改表结构,删除表时,其他事务对这表的增删改查发生阻塞。这是由MDL锁实现的,MDL锁也分为读锁和写锁,在进行crud操作的时候,会加MDL读锁,进行DDL的时候会加MDL写锁。

    我们需要注意MDL读锁写锁是互斥的

    image-20221127220158968

    如图四个不同的session先后依次执行语句,其中A,B都是获取MDL读锁,互不阻塞,随即C获取MDL写锁,这时候C会被阻塞,这一阻塞不打紧,还会阻塞后续获取MDL读锁的事务,造成整个表不可用。这启发了我们,在做DDL的时候要解决长事务,事务不提交,就会一直占着MDL锁。在 MySQL 的 information_schema 库的innodb_trx 表中,可以查到当前执行中的事务。如果要做 DDL 变更的表刚好有长事务在执行,要考虑先暂停 DDL,或者 kill 掉这个长事务。

    五丶innodb 行锁#

    1.Record Lock#

    官方名称Lock_REC_NOT_GAP

    image-20221128071544241

    记录锁有S锁和X锁,S型记录锁之间可以共享,X型记录锁和S型记录锁,X型记录锁互斥

    2.GAP Lock#

    innodb的可重复读级别,使用词锁解决幻读问题,前面我们说过,其难点在于,加锁的时候幻影记录还未出现。官方使用Lock_GAP实现如下操作

    image-20221128071959076

    此处的gap锁可以反之其他事务在number为8记录前面的间隙插入新的记录,在区间(3,8)内无法进行插入操作,当另外一个事务要插入number为4的记录时,首先需要定位到该条记录的下一条记录,也就是number为8的记录,此时number为8的记录具备gap锁,所以将阻塞插入操作,直到gap锁被释放,其他事务才能进行插入。
    gap锁出现的目的,就是为了防止插入幻影记录,如果对记录上gap锁,并不会限制其他事务对记录加记录锁

    innodb有两个虚拟的记录Infimum(虚拟最小),Supermun(虚拟最大)当我们想在(xx,正无穷)范围锁住幻影记录时就可以对Supermun加gap锁。

    3.Next-Key Lock#

    Next-Key Lock = 记录锁 + gap锁,既锁住记录,也锁住记录之前的间隙

    image-20221128072929035

    4.Insert Intention Lock#

    插入意向锁,表示事务想在某个间隙插入新的记录,但是当前处于等待状态。

    比如事务A持有(4,8)范围内的gap锁,事务B和C,想插入(4,8)范围内的记录,就会在内存中生成事务B,C对应的插入意向锁,当前事务A释放gap锁的时候,将唤醒事务B和C,事务B和C可以同时获取插入意向锁,然后进行插入。插入意向锁并不会阻止对记录继续上锁。

    5.隐式锁#

    为事务生成内存中的锁结构并不是一个0成本的事情,为了节省这个成本,提出隐式锁的概念。

    当一个事务插入语一条记录A,其他事务

    1. select xxx Lock in share mode读取记录A(获取记录A的S锁),或者使用select xxx for update(获取记录A的X锁)
    2. 立即修改记录A(获取x锁)

    对于聚簇索引来说,有一个隐藏列trx_id此列存储着最后更改记录的事务id,在当前事务A插入记录后,便是存储着当前事务A的id,其他事务B企图获取x锁,s锁的时候,就需要下先看一下,trx_id隐藏列对应的事务是否存活,如果不是那么正常获取,反之需要为当前事务A创建一个x锁内存结构,并标记is_waiting为false,然后事务B将为自己创建一个锁结构,is_waiting 为true然后事务B进入等待状态

    对于二级索引来说,其不具备隐藏列trx_id但是在二级索引页面的page header中的page_maxt_trx_id属性,记录了改动页面最大的事务id,如果其属性值小于当前最小的活跃事务id,那么说明对页面的改动事务已经提交,否则需要定位到二级索引记录,然后回表对聚簇索引进行上述聚簇索引的操作。

    一个事务对新插入的记录不需要显示的加锁,由于事务id的存在相当于加了一个隐式锁,别的事务需要加S锁或者X锁的时候,先帮之前的事务生成锁结构,然后为自己生成锁结构,再进入阻塞状态。隐式锁起到了延迟加锁的作用,也许别的事务不会获取于隐式锁冲突的锁,这时候可以减少内存中生成锁结构。

  • 相关阅读:
    在KubeSphere启动服务网格Istio并解决解决ContainerCreating问题
    qcc51xx --- 规则(rules)机制详解
    x86汇编_压缩BCD码_DAA指令_DAS指令_笔记57
    【luogu CF1609G】A Stroll Around the Matrix(贪心)(线段树)
    从创意造型到高品质曲面的卓越体验|CATIA ICEM Design Experience
    性能测试 —— JMeter分布式测试及其详细步骤
    java计算机毕业设计高校设备采购审批管理系统源码+系统+lw文档+mysql数据库+部署
    MySql 事务隔离实现:
    02矩阵01 —— 基本矩阵 : 对角矩阵、方幂、数量矩阵、转置矩阵、对称矩阵、逆矩阵、奇异矩阵、三角矩阵
    单目深度估计: LDRN网络及代码讲解
  • 原文地址:https://www.cnblogs.com/cuzzz/p/16934334.html