数据库的锁——思想概念篇
文章目录
数据库的锁
锁是数据库系统区别于文件系统的一个关键特性,锁机制用于管理对共享资源的并发访问。
下面我们以MySQL数据库的InnoDB引擎为例,来说明锁的一些特点。
锁的类型:
InnoDB存储引擎实现了如下两种标准的行级锁:
- 共享锁(S Lock),允许事务读一行数据。
- 排他锁(X Lock),允许事务删除或更新一行数据。
例如:如果一个事务T1已经获得了行 r 的共享锁,那么另外的事务T2可以立即获得 行r 的共享锁,因为读取并没有改变行r的数据,称这种情况为锁兼容。但若有其他的事务T3想获得 行r 的排他锁,则其必须等待事务T1、T2释放行 r 上的共享锁,这种情况称为锁不兼容。
下图显示了共享锁和排他锁的兼容性,可以发现X锁与任何的锁都不兼容,而S锁仅和S锁兼容。
需要特别注意的是,S和X锁都是行锁,兼容是指对同一记录(row)锁的兼容性情况。
锁的粒度:
InnoDB存储引擎支持多粒度锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。
为了支持在不同粒度上进行加锁操作,InnoDB存储引擎支持一种额外的锁方式,称之为意向锁。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁。
InnoDB存储引擎支持意向锁设计比较简练,其意向锁即为表级别的锁。设计目的主要是为了在一个事务中揭示下一行将被请求的锁类型。
其支持两种意向锁:
- 意向共享锁(IS Lock),事务想要获得一张表中某几行的共享锁。
- 意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁。
由于InnoDB存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫以外的任何请求。
故表级意向锁与行级锁的兼容性如下图所示:
锁的算法:
InnoDB存储引擎有3种行锁的算法,其分别是:
1. 行锁(Record Locks)
行锁,顾名思义,就是只会锁住某一行的数据,行级锁能大大减少数据库操作的冲突。
其加锁粒度最小,但加锁的开销也最大。有可能会出现死锁的情况。产生行锁一般是等值查询,且作用于唯一索引或连续的普通索引(普通索引列的值没有间隔)
※ InnoDB中行级锁是怎么实现的?
InnoDB行级锁是通过给索引上的索引项加锁来实现的。
只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。当表中锁定其中的某几行时,不同的事务可以使用不同的索引锁定不同的行。另外,不论使用主键索引、唯一索引还是普通索引,InnoDB都会使用行锁来对数据加锁。
2. 间隙锁(Gap Locks)
间隙锁是封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围(不包括记录本身)。
可能产生间隙锁的条件:
- 使用普通索引锁定;
- 使用多列唯一索引;
- 使用唯一索引锁定多行记录。
间隙锁可以通过参数innodb_locks_unsafe_for_binlog
开闭,它的作用是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生。
3. 临键锁(Next-key Locks)
临键锁是行锁和间隙锁的组合,它的封锁范围,既包含索引记录本身(行锁),又包含索引区间(间隙锁)。
无论是唯一索引或是普通索引都有可能产生临键锁。
RR隔离级别,默认是临键锁,临键锁根据不同的作用范围,可以转换为行锁和间隙锁,在RC隔离级别下,它和间隙锁会失效。
关于死锁:
死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。若无外力作用,事务都将无法推进下去。
比如说:事务A等待事务B、事务B等待事务A,这种死锁问题被称为AB-BA死锁,下图演示了死锁的这一种经典的情况:
解决死锁:
方法一: 解决死锁问题最简单的一种方法是超时,即当两个事务互相等待时,当一个等待时间超过设置的某一阈值时,其中一个事务进行回滚,另一个等待的事务就能继续进行。
方法二:除了超时机制,当前数据库还都普遍采用 wait-for graph(等待图)
的方式来进行死锁检测。较之超时的解决方案,这是一种更为主动的死锁检测方式。InnoDB存储引擎也采用的这种方式。
wait-for graph(等待图)要求数据库保存以下两种信息:
- 锁的信息链表;
- 事务等待链表;
通过上述链表可以构造出一张图,而在这个图中若存在回路,就代表存在死锁,因此资源间相互发生等待。这是一种较为主动的死锁检测机制,在每个事务请求锁并发生等待时都会判断是否存在回路,若存在则有死锁,通常来说InnoDB存储引擎选择回滚undo量最小的事务
比如下图就很形象,事务T1,T2,T3发生了死锁,InnoDB中存储的锁与事务链表构成的等待图就会发生回路,此时就能检测到死锁发生,然后InnoDB就会回滚事务。
锁的升级:
锁升级(Lock Escalation)是指将当前锁的粒度降低。
举例来说,数据库可以把一个表的1000个行锁升级为一个页锁,或者将页锁升级为表锁。
InnoDB存储引擎不存在锁升级的问题。因为其不是根据每个记录来产生行锁的,相反,InnoDB存储引擎是根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。因此不管一个事务锁住页中一个记录还是多个记录,其开销通常都是一致的。