mysql技术内幕-锁
发表于:2025-03-15 | 分类: 学习

什么是锁

锁是数据库系统区别于文件系统的一个关键特性。锁机制用于管理对共享资源的并发访问。innodb在行级别上对表数据上锁,这固然不错。不过innodb也会在数据库内部其他多个地方使用锁,从而允许对多种不同资源提供并发访问。例如操作缓冲池中的LRU列表,删除、添加、移动LRU列表中的元素,为了保证一致性,必须有锁的介入。数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。

不同数据库锁的实现方式不同

lock与latch

latch一般称为闩锁(轻量级锁),因为其锁定的时间必须非常短。若持续的时间长,则应用的性能会非常差。在innodb中latch可以分为mutex(互斥锁)和rwlock(读写锁)。其目的是用来保证并发线程操作临界资源的正确性,并且通常没有死锁检测的机制。

lock的对象是事务,用来锁定的是数据库中的对象,如表、页、行。并且一般lock的对象仅在事务commit或rollback后进行释放(不同的隔离级别释放的时间可能不同)。lock有死锁机制,下表展示了lock与latch的不同:

InnoDB存储引擎中的锁

  1. 锁的类型
    innodb实现了如下两种标准的行级锁:

    • 共享锁(S lock), 允许事务度一行数据。
    • 排他锁(X lock),允许事务删除或更新一行数据。

    下表展示了排他锁和共享锁的兼容性:
    x&s

    innodb支持多粒度锁定,这种锁允许事务在行级别上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,innodb支持一种额外的锁方式,称之为意向锁(Intention Lock)。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁。

    innodb支持两种意向锁:

    1. 意向共享锁(IS Lock),事务想要获得一张表中某几行的共享锁

    2. 意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁

    表级意向锁和行级锁的兼容性如下表:

  2. 一致性非锁定读
    一致性非锁定读(consistent nonlocking read)是指innodb存储引擎通过行多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据。如果正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放。相反innodb回去读取行的一个快照数据。

  3. 一致性锁定读
    innodb对于SELECT语句支持两种一致性的锁定读(locking read)操作:

    • SELECT…FOR UPDATE
    • SELECT…LOCK IN SHARE MODE

    SELECT…FOR UPDATE对读取的行记录加一个x锁,其他事务不能对已锁定的行加上任何锁。SELECT…LOCK IN SHARE MODE对读取的纪录加一个s锁,其他事务可以向被锁定的行加s锁,但是如果加x锁,就会阻塞。

    因为这两个语句必须在一个事务中,当事务提交了,锁也就释放了。因此在使用两个SELECT锁定语句时,务必加上BEGIN, START TRANSACTION或者SET AUTOCOMMIT=0。

  4. 自增长与锁
    这种锁采用一种特殊的表锁机制,为了提高插入性能,锁不是在一个事务完成后释放,而是在完成对自增长值插入sql语句后立即释放。

  5. 外键和锁
    在innodb中对于一个外键列,如果没有显式地对这个列加索引,innodb会自动加上一个索引,这样可以避免表锁。

锁的算法

  1. 行锁的3种算法
    innodb有三种行锁的算法:

    • Record Lock: 单个行记录上的锁
    • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
    • Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身

    Record Lock总是回去锁住索引记录,如果innodb没有在建表的时候设置任何索引,会使用隐式的主键来锁定。

    Next-Key Lock是结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,innodb对于行的查询都是采用这种锁定算法。
    对于唯一键值的锁定,Next-Key Lock降级为Record Lock仅存在于查询所有的唯一索引队列。若唯一索引由多个列组成,而查询仅是查找多个唯一索引列中的其中一个,那么查询其实是range类型查询,而不是point类型查询,故innodb依然使用Next-Key Lock进行锁定。

  2. 解决Phantom Problem
    Phantom Problem是指在同一事务下,连续执行两次同样的sql语句可能导致不同的结果,第二次的sql语句可能回返会之前不存在的行。
    innodb采用Next-Key Locking的算法避免Phantom Problem。

锁问题

锁可以实现事务的隔离性要求,使得事务可以并发的工作。锁提高了并发,但是却会带来潜在问题。不过好在因为事务隔离性的要求,锁只会带来三种问题,如果可以防止这三种情况的发生,那将不会产生并发异常。

  1. 脏读
    脏数据是指事务对缓冲池中行记录的修改,并且还没有被提交。
    脏读是指未提交的数据,如果读到了脏数据,即一个事务可以读到另外一个事务中未提交的数据,显然违反了数据库的隔离性。
  2. 不可重复读
    不可重复读是指在一个事务内多次读取同一个数据集合。在这个事务还没有结束时,另外一个事务也访问该同一数据集合,并做了一些DML操作。因此,在第一个事务中的两次读取数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。
    不可重复读和脏读的区别是:脏读是读到未提交地数据,而不可重复读读到的是已经提交的数据。但是其违反了数据库事务一致性的要求。
  3. 丢失更新
    丢失更新是另一个锁导致的问题,简单来说就是一个事务的更新操作会被另一个事务的更新操作覆盖,从而导致数据不一致。

阻塞

因为不同锁之间的兼容性关系,在有些时刻一个事务中的锁需要等待另一个事务中的锁释放它所占用的资源,这就是阻塞。阻塞并不是一键坏事,其是为了确保事务可以并发且正常地运行。

死锁

  1. 死锁的概念
    死锁是指两个或两个以上的事务在执行过程中,因为争夺锁资源而造成的一种互相等待的现象。若无外力作用,事务都将无法推进下去。解决死锁问题最简单的方式是不要有等待,将任何的等待都转化为回滚,并且事务重新开始。但是会导致并发性能下降。
    解决死锁问题最简单的一种方法是超时。
    除了超时机制,当前数据库普遍采用wait-for graph(等待图)的方式进行死锁检测。
    wait-for graph要求数据库保存以下两种信息:

    • 锁的信息链表
    • 事务等待链表

    通过上述链表可以构造出一张图,而在这个图中若存在回路,就代表存在死锁,因此资源间相互发生等待。在wait-for graph中,事务为图中的节点。而在图中事务T1指向T2边的定义为:

    • 事务T1等待事务T2所占用的资源
    • 事务T1最终等待T2所占用的资源,也就是事务之间在等待相同的资源,而事务T1发生在事务T2的后面。
  2. 死锁概率
    死锁应该是非常少发生,若经常发生,则系统不可用。死锁的次数应该还要少于等待,因为至少需要2次等待才会产生一次死锁。

  3. 死锁的示例
    如果程序是串行的,那么不可能发生死锁。死锁只存在于并发的情况,而数据库本身就是一个并发运行的程序,因此可能会发生死锁。

锁升级

锁升级(Lock Escalation)是指将当前锁的粒度降低。innodb不存在锁升级问题。因为其根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。

上一篇:
mysql技术内幕-事务
下一篇:
mysql技术内幕-索引与算法