MySQL技术内幕笔记0x03

锁简介

  • 锁是数据库区别于文件系统的一个关键特性。数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。

lock与latch

  • latch一般成为闩锁(轻量级锁),因为其要求锁定的时间必须非常短。若持续时间长,则应用的性能会非常差。其目的是保证并发线程操作临界资源的正确性,并且通常没有死锁检测机制。在InnoDB中,latch又可以分为mutex(互斥锁)和rwlock(读写锁)。
  • lock的对象是是事务,用来锁定的是数据库中的对象,如表、页、行。一般lock的对象仅在事务commit或rollback后进行释放(不同事务隔离级别释放的时间可能不同)。此外,lock是有死锁机制的。

InnoDB存储引擎中的锁

锁的类型

  • InnoDB实现了如下两种标准的行级锁:
    • 共享锁(S Lock),允许事务读一行数据。
    • 排他锁(X Lock),允许事务删除或更新一行数据。
  • InnoDB支持多粒度锁定,这种锁定允许事务在行级锁与表级锁同时存在。为了支持在不同粒度上进行加锁操作,InnoDB支持一种额外的锁方式,称之为意向锁。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁。InnoDB支持两种意向锁:
    • 意向共享锁(IS Lock),事务想要夺得一张表中某几行的共享锁。
    • 意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁。
  • 意向锁不会阻塞除全表扫描以外的任何请求。表级意向锁之间互相兼容,IS与S兼容,S与S兼容,其他情况都不兼容。

一致性非锁定读

  • 一致性非锁定读是指InnoDB通过行的多版本控制的方式来读取当前数据库中行的数据。如果读取的行正在执行delete或update操作,这时读取操作不会因此去等待行锁释放,而是去读取行的快照数据。该操作实现是通过undo段来完成的,而undo用来在事务中回滚数据,因此快照数据本身是没有额外开销的。此外,读取快照数据是不需要上锁的,因为没有事务需要对历史数据进行修改。由此,非锁定读机制极大地提高了数据库的并发性。

一致性锁定读

  • InnoDB对于select语句支持两种一致性锁定读操作,一种是select ... for update,对读取的行记录加一个X锁,其他事务不能对已锁定的行加上任何锁。另一种是select ... lock in share mode,对读取的行记录加一个S锁,其他事务可以向被锁定的行加S锁,但是如果加X锁则会被阻塞。

锁的算法

行锁的3种算法

  • Record Lock:单个行记录上锁。总是会去锁住索引记录,如果表在建立时没有设置索引,那么InnoDB会使用隐式的主键来进行锁定。
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。
  • Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身。InnoDB对于行的查询都是采用这种锁定算法。其设计的目的是为了解决幻象问题。
  • 当查询的索引含有唯一属性时,InnoDB会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住记录,从而提高并发性。

解决幻象问题

  • 幻象问题是指,在同一事务下,连续执行两次相同的SQL语句可能得到不同的结果,第二次SQL语句可能会返回之前不存在的的行。

锁问题

脏读

  • 脏数据是指事务对缓冲池中行记录的修改,并且还没有被提交。
  • 脏读是指在不同事务下,当前事务可以读到其他事务未提交的数据,即脏数据。

不可重复读

  • 不可重复读是指,在同一个事务中多次读取同一数据集合,但是存在两次结果不一致的情况。
  • 不可重复读和脏读的区别是,脏读得到的是未提交的数据,而不可重复读得到的是已经提交的数据,但是其违反了数据库事务一致性的要求。
  • 不可重复读的问题是可以接受的,因为其读到的是已经提交的数据,本身不会带来很大问题。

丢失更新

  • 丢失更新是指有些事务的更新操作被另一个事务的更新操作所覆盖,从而导致数据的不一致,例如:
    1. 事务t1将行记录r更新为v1,但是t1没有提交
    2. 事务t2将行记录r更新为v2,但是t2没有提交
    3. t1提交
    4. t2提交
  • 要避免丢失更新发生,需要让事务在这种情况下的操作变成串行化。在1.和2.中分别加上一个X锁,据此,2.需要等待1.和3.结束才能执行。

死锁

  • 死锁是指两个及以上的事务在执行过程中,因竞争锁资源而造成的一种互相等待的现象。
  • 解决死锁最简单的方式是不要有等待,让任何等待都转化为回滚,并且事务重新开始。但是这种方式可能导致并发性能下降,而这所带来的问题更为严重,因为很难被发现并且浪费资源。
  • 解决死锁的一种方法是超时,即当两个事务互相等待时,当一个等待时间超过设置的某一阈值时,其中一个事务进行回滚,另一个等待事务就能继续进行。
  • 当前数据库普遍采用等待图的方法进行死锁检测,这是一种更为主动的检测方法,主要包括锁的信息链表和事务的等待链表,以及两者构造的图,如果这张图中存在回路,就代表存在死锁。检测算法通常采用深度优先算法实现。
Author: SinLapis
Link: http://sinlapis.github.io/2019/09/05/MySQL技术内幕笔记0x03/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.