【mysql】- 锁篇


回顾

问题

  • 事务并发执行时可能带来各类问题,并发事务访问相同记录的状况大体能够划分为3种
    • 读-读状况:即并发事务相继读取相同的记录
      • 读取操做自己不会对记录有什么影响,并不会引发什么问题,因此容许这种状况的发
    • 写-写状况:即并发事务相继对相同的记录作出改动
      • 任何一种隔离级别都不容许这种问题的发生。因此在多个未提交事务相继对一条记录作改动时,须要让它们排队执行,这个排队的过程实际上是经过来实现的。这个所谓的实际上是一个内存中的结构,在事务执行前原本是没有锁的,也就是说一开始是没有锁结构和记录进行关联的
    • 读-写写-读状况:也就是一个事务进行读取操做,另外一个进行改动操做
      • 这种状况下可能发生脏读不可重复读幻读的问题

解决上述问题方案

  • 读操做利多版本并发控制( MVCC ),写操做进行加锁
  • 读、写操做都采用加锁的方案

一致性读(Consistent Reads)

  • 事务利用MVCC 进行的读取操做称之为一致性读,或者一致性性锁读,有的地也称之为快照读。全部普通的SELECT语句( plain SELECT )在READ COMMITTEDREPEATABLE READ隔离级别下都算是一致性读一致性读并不会对表中的任何记录作加锁操做,其余事务能够自由的对表中的记录作改动

锁定读(Locking Reads)

并发事务的读-读状况并不会引发什么问题,不过对于写-写读-写写-读这些状况可能会引发一些问题,须要使用MVCC或者加锁的方式来解决它们。在使用加锁的方式解决问题时,因为既要容许读-读状况不受影响,又要使写-写读-写写-读状况中的操做相互阻塞并发

  • 共享锁和独占锁指针

    • 共享锁 ,英文名:Shared Locks,简称S锁。在事务要读取一条记录时,须要先获取该记录的S锁
    • 独占锁 ,也常称排他锁,英文名:Exclusive Locks,简称X锁。在事务要改动一条记录时,须要先获取该记录的X锁
    • 上述两种锁的场景
      • 假如事务T1首先获取了一条记录的S锁以后,事务T2接着也要访问这条记录:
        • 若是事务T2想要再获取一个记录的S锁,那么事务T2也会得到该锁,也就意味着事务T1T2在该记录上同时持有S锁
        • 若是事务T2想要再获取一个记录的X锁,那么此操做会被阻塞,直到事务T1提交以后将S锁释放掉
      • 若是事务T1首先获取了一条记录的X锁以后,那么无论事务T2接着想获取该记录的S锁仍是X锁都会被阻塞,直到事务T1提交
      • 故咱们说S锁S锁是兼容的,S锁X锁是不兼容的,X锁X锁也是不兼容的
  • 锁定读的语句code

    • 在采用加锁方式解决脏读不可重复读幻读这些问题时,读取一条记录时须要获取该下该记录的S锁,其实这是不严谨的,有时候想在读取记录时就获取记录的X锁,来禁用别的事务读写该记录
      • 对读取的记录加S锁
        • 也就是在普通的SELECT语句后边加,若是当前事务执行了该语句,那么它会为读取到的记录加S锁,这样容许别的事务继续获取这些记录的S锁(比方说别的事务也使用语句来读取这些记录),可是不能获取这些记录的X锁(比方说使SELECT ... FOR UPDATE语句来读取这些记录,或者直接修改这些记录)。若是别的事务想要获取这些记录的X锁,那么它们会阻塞,直到当前事务提交以后将这些记录上的S锁释放掉
      • 对读取的记录加X锁
        • SELECT ... FOR UPDATE; 也就是在普通的SELECT语句后边加FOR UPDATE,若是当前事务执行了该语句,那么它会为读取到的记录加X锁,这样既不容许别的事务获取这些记录的S锁(比方说别的事务使用语句来读取这些记录),也不容许获取这些记录的X锁(比方说使用SELECT ... FOR UPDATE语句来读取这些记录,或者直接修改这些记录)。若是别的事务想要获取这些记录的S锁或者X锁,那么它们会阻塞,直到当前事务提交以后将这些记录上的X锁释放掉
  • 写操做索引

    • 日常所用到的写操做DELETEUPDATEINSERT这三种:
      • DELETE:对一条记录作DELETE操做的过程实际上是先在B+树中定位到这条记录的位置,而后获取一下这条记录的X锁,而后再执行delete mark操做。咱们也能够把这个定位待删除记录在B+树中位置的过程当作是一个获取X锁的锁定读
      • UPDATE:
        • 在对一条记录作UPDATE操做时分为三种状况:
          • 若是未修改该记录的键值而且被更新的列占用的存储空间在修改先后未发生变化,则先在B+树中定位到这条记录的位置,而后再获取一下记录的X锁,最后在原记录的位置进行修改操做。其实咱们也能够把这个定位待修改记录在B+树中位置的过程当作是一个获取X锁锁定读
          • 若是未修改该记录的键值而且至少有一个被更新的列占用的存储空间在修改先后发生变化,则先在B+树中定位到这条记录的位置,而后获取一下记录的X锁,将该记录完全删除掉(就是把记录完全移入垃圾链表),最后再插入一条新记录。这个定位待修改记录在B+树中位置的过程当作是一个获取X锁锁定读,新插入的记录由 INSERT操做提供的隐式锁进行保护
          • 若是修改了该记录的键值,则至关于在原记录上作DELETE操做以后再来一次INSERT操做,加锁操做就须要按照DELETEINSERT的规则进行了
      • NSERT:通常状况下,新插入一条记录的操做并不加锁,经过一种称之为隐式锁来保护这条新插入的记录在本事务提交前不被别的事务访问

前边提到的都是针对记录的,也能够被称之为行级锁或者行锁,对一条记录加锁影响的也只是这条记录而已,咱们就说这个锁的粒度比较细;其实一个事务也能够在表级别进行加锁,天然就被称之为表级锁或者表锁,对一个表加锁影响整个表中的记录,咱们就说这个锁的粒度比较粗。给表加的锁也能够分为共享锁S锁)和独占锁X锁事务

  • 多粒度锁
    • 给表加S锁
      • 若是一个事务给表加了S锁,那么:
        • 别的事务能够继续得到该表的S锁
        • 别的事务能够继续得到该表中的某些记录的S锁
        • 别的事务不能够继续得到该表的X锁
        • 别的事务不能够继续得到该表中的某些记录的X锁
    • 给表加X锁
      • 若是一个事务给表加了X锁(意味着该事务要独占这个表),那么:
        • 别的事务不能够继续得到该表的S锁
        • 别的事务不能够继续得到该表中的某些记录的S锁
        • 别的事务不能够继续得到该表的X锁
        • 别的事务不能够继续得到该表中的某些记录的X锁

InnoDB存储引擎中的锁

  • 表级锁
    • 表级别的S锁X锁
      • 在对某个表执行SELECTINSERTDELETEUPDATE语句时,InnoDB存储引擎是不会为这个表添加表级别的S锁或者X锁
    • 表级别的IS锁IX锁
      • 当咱们在对使用InnoDB存储引擎的表的某些记录加S锁以前,那就须要先在表级别加一个IS锁,当咱们在对使用InnoDB 存储引擎的表的某些记录加X锁以前,那就须要先在表级别加一个IX锁IS锁IX锁的使命只是为了后续在加表级别的S锁X锁时判断表中是否有已经被加锁的记录,以免用遍历的方式来查看表中有没有上锁的记录
    • 表级别的AUTO-INC锁
      • 在使用MySQL过程当中,咱们能够为表的某个列添加AUTO_INCREMENT属性,以后在插入记录时,能够不指定该列的值,系统会自动为它赋上递增的值
  • 行级锁:
    • 行锁,也称为记录锁 ,顾名思义就是在记录上加的锁
    • 行锁类型
      • Record Locks:有S锁X锁之分,当一个事务获取了一条记录的S型记录锁后,其余事务也能够继续获取该记录的S型记录锁,但不能够继续获取X型记录锁;当一个事务获取了一条记录的X型记录锁后,其余事务既不能够继续获取该记录的S型记录锁,也不能够继续获取X型记录锁
      • Gap Locks:能够用于解决可重复读级别下的幻读问题,防止插入幻影记录
      • Next-Key Locks: 锁住某条记录同时又能够阻止其余事务在该条记录前边的间隙插入新纪录,其是Record Locksgap Locks的合体它既能保护该条记录,又能阻止别的事务将新记录插入被保护记录前边的间隙
      • Insert Intention Locks:一个事务在插入一条记录时须要判断一下插入位置是否是被别的事务加了所谓的gap锁,若存在,插入操做须要等待,直到拥有的gap锁对应的事务提交,事务在等待的时候也须要在内存中生成一个锁结构,代表有事务想在某个间隙中插入新纪录,可是如今在等待。这种类型的锁命名为Insert Intention Locks,也成为插入意向锁
      • 隐式锁:一个事务在执行INSERT操做时,若是即将插入的间隙已经被其余事务加了gap锁,那么本次INSERT操做会阻塞,而且当前事务会在该间隙上加一个插入意向锁,不然通常状况下INSERT操做是不加锁的

内存结构

  • 加锁的本质就是在内存中建立一个锁结构与之关联
    • 对于须要放在同一个锁结构中的状况
      • 在同一个事务中进行加锁操做
      • 被加锁的记录在同一个页面中
      • 加锁的类型是同样的
      • 等待状态是同样的
  • 锁结构
    • 锁所在的事务信息: 不管是表锁仍是行锁,都是在事务执行过程当中生成的,哪一个事务生成了这个锁结构,这里就记载着这个事务的信息。其本质是一个指针,经过该指针能够找到内存中关于该事务的更多信息
    • 索引信息:对于行锁来讲,须要记录一下加锁的记录是属于哪一个索引的
    • 表锁/行级信息
      • 表锁:记载着这是对哪一个表加的锁,还有其余的一些信息
      • 行锁:记载了三个重要的信息
        • Space ID :记录所在表空间。
        • Page Number :记录所在页号。
        • n_bits :对于行锁来讲,一条记录就对应着一个比特位,一个页面中包含不少记录,用不一样的比特位来区分究竟是哪一条记录加了锁。为此在行锁结构的末尾放置了一堆比特位,这个n_bits属性表明使用了多少比特位
    • type_mode:这是一个32位的数,被分红了lock_modelock_typerec_lock_type三个部分
      • 锁的模式( lock_mode ),占用低4位
        • LOCK_IS(十进制的0):表示共享意向锁,也就是IS锁
        • LOCK_IX(十进制的1):表示独占意向锁,也就是IX锁
        • LOCK_S(十进制的2):表示共享锁,也就是S锁
        • LOCK_X(十进制的3):表示独占锁,也就是X锁
        • LOCK_AUTO_INC(十进制的4):表示AUTO-INC锁
      • 锁的类型( lock_type ),占用第5〜8位,不过现阶段只有第5位和第6位被使用:
        • LOCK_TABLE(十进制的16),也就是当第5个比特位置为1时,表示表级锁。
        • LOCK_REC(十进制的32),也就是当第6个比特位置为1时,表示行级锁
      • 行锁的具体类型(rec_lock_type),使用其他的位来表示。只有在lock_type的值为LOCK_REC时,也就是只有在该锁为行级锁时,才会被细分为更多的类型:
        • LOCK_ORDINARY(十进制的0):表示next-key锁 。
        • LOCK_GAP(十进制的512):也就是当第10个比特位置为1时,表示gap锁
        • LOCK_REC_NOT_GAP(十进制的1024):也就是当第11个比特位置为1时,表示记录锁
        • LOCK_INSERT_INTENTION(十进制的2048):也就是当第12个比特位置为1时,表示插入意向锁
相关文章
相关标签/搜索