InnoDB的锁机制

笔记摘自《MYSQL技术内幕(InnoDB存储引擎)》

1 什么是锁

锁是数据库系统区别于文件系统的一个关键特性。
锁机制用于管理对共享资源的并发访问,为数据的完整性和一致性提供保证。
MyISAM引擎,其锁是表锁设计,并发情况下的读没有问题,但是并发插入的性能就要差一些。
InnoDB存储引擎锁的实现和Oracle数据库非常类似,提供一致性的非锁定读,行级锁支持。行级锁没有相关额外的开销,并可以同时得到并发性和一致性。

2. lock和latch

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

lock的对象是事务,用来锁定的是数据库的对象,如表、页、行。并且一般lock的对象仅在事务commit或rollback后进行释放(不同事务隔离级别释放的时间可能不同)。此外,lock,正如在大多数数据库中一样,是有死锁机制的。

《InnoDB的锁机制》 image.png

在InnoDB存储引擎中的latch,可以通过命令

SHOW ENGINE INNODB MUTEX

来进行查看。

3. InnoDB存储引擎中的锁

3.1 锁的类型

InnoDB实现了两种行级锁:

  • 共享锁(S Lock)允许事务读一行数据
  • 排它锁(X Lock)允许事务删除或更新一行数据

如果一个事务已经获得了行r的共享锁,那么另外的事务T2可以立即获得行r的共享锁,因为读取并没有改变行r的数据,称这种情况为锁兼容。
但若有其它的事务T3想获得行r的排它锁,那么必须等待T1,T2释放行r上的共享锁,这种情况称为锁不兼容。

《InnoDB的锁机制》 image.png

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

若将上锁的对象看成一棵树,那么对最下层的对象上锁,也就是对最细粒度的对象进行上锁,那么首先需要对粗粒度对象上锁。如果需要对页上的记录r进行上X锁,那么分别需要对A、表、页上意向锁IX,最后对记录r上X锁。若其中任务一部分导致等待,那么该操作需要等待粗粒度锁的完成。举例来说,在对记录r加X锁之前,已经有事务对表I进行了S表锁,那么表I上已存在S锁,之后事务需要对记录r在表I上加IX,由于不兼容,所以该事务需要等待表锁操作的完成。

《InnoDB的锁机制》 image.png

InnoDB存储引擎支持意向锁设计笔记精炼,其意向锁即为表级锁。设计目的主要是为了在一个事务中揭示下一行将被请求的锁类型。其支持两种意向锁:
1)意向共享锁(IS Lock): 事务想要获得一张表中某几行的共享锁;
2)意向排他锁(IX Lock):事务想要获得一张表中某几行的排他锁。
由于InnoDB支持行级锁,因此意向锁其实不会阻塞全表扫描以外的任何请求。故表级意向锁与行级锁的兼容性如下表:

《InnoDB的锁机制》 image.png

在InnoDB 1.0版本之前,用户只能通过命令SHOW FULL PROCESSLIST,SHOW ENGINE INNODB STATUS等来查看当前数据库中锁的请求,然后再判断事务锁的情况。 现在,INFORMATION_SCHEMA架构下添加了表INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS。通过三张表,用户可以更简单地监控当前事务并分析可能存在的锁问题。

《InnoDB的锁机制》 image.png

《InnoDB的锁机制》 image.png

《InnoDB的锁机制》 image.png

3.2 一致性非锁定读

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

《InnoDB的锁机制》 image.png

上图直观展现了InnoDB一致性的非锁定读。快照数据是指该行的历史版本数据,该实现是通过undo段来实现的。而undo用来在事务中回滚数据,因此快照数据本身是没有额外的开销。

3.3 一致性锁定读

select语句的两种一致性锁定读:

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

3.4 自增长与锁

InnoDB存储引擎提供了一种轻量级互斥量的自增长实现机制,大大提高了增长值插入的性能。

《InnoDB的锁机制》 image.png

3.5 外键和锁

4. 锁的算法

4.1 行锁的三种算法

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

Record Lock总是会去锁住索引记录,如果InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时InnoDB会使用隐式的主键来进行锁定。

Next-Key Lock结合了两种锁,InnoDB对于行的查询都是采用这种锁定算法。当查询的索引含有唯一属性时,InnoDB会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是范围。

可以用以下两种方式显示关闭Gap Lock:

  • 事务隔离级别设置为其它,如READ COMMITED
  • 将参数innodb_locks_unsafe_for_binlog设置为1

4.2 解决Phantom Problem(幻读)

Phantom Problem是指同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行。

InnoDB采用Next-Key Lock的算法避免Phantom Problem。

5. 锁问题

5.1 脏读

在理解脏读之前,要理解脏数据的概念。但是脏数据和之前所介绍的脏页完全是两种不同的概念。脏页指的是在缓冲池中已经被修改的页,但是还没有刷新到磁盘中, 即数据库实例内存中的页和磁盘中的页的数据是不一致的,当然在刷新到磁盘之前,日志都已经都被写入到了重做日志文件中。而所谓脏数据是指事务对缓冲池中行记录的修改,并且还没有被提交。

对于脏页的读取,是非常正常的。脏页是因为数据库实例内存和磁盘的异步造成的,这并不影响数据的一致性。并且因为脏页的刷新是异步的,不影响数据库的可用性,因此可以带来性能的提高。

脏数据却截然不同,脏数据是指未提交的数据,如果读到了脏数据,即一个事务可以读到另外一个事务中未提交的数据,则显然违反了数据库的隔离性。

5.2 不可重复读

指在一个事务内多次读取同一数据的集合,在这个事务还没有结束时,另外一个事务也访问该同一数据集合,并做了一些DML操作。因此,在第一个事务中的两次读数据,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的情况。

不可重复读和脏读的区别:脏读是读到未提交的数据,不可重复读是读到已提交的数据。

5.3 丢失更新

丢失更新是另外一个锁导致的问题,简单来说其就是一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致。
例如:

《InnoDB的锁机制》 image.png

《InnoDB的锁机制》 image.png

6. 阻塞

因为不同锁之间的兼容性关系,在有些时刻一个事务中的锁需要等待另一个事务中的锁释放它所占用的资源,这就是阻塞。阻塞不一定是坏事,是为了确保事务可以并发正常地进行。
在InnoDB的存储引擎中,参数innodb_lock_wait_timeout用来控制等待的时间(默认50秒)。innodb_rollback_on_timeout用来设定是否在等待超时时对进行中的事务进行回滚操作(默认是OFF,代表不回滚)。

7.死锁

死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象。
其实我觉得何登成的这篇加锁分析比书里写的还好。

    原文作者:10xjzheng
    原文地址: https://www.jianshu.com/p/2b41693aac11
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞