在数据库事务特性ACID中,锁对应其中的I(Isolation 隔离性)。

1. 锁类型

存储引擎实现了两种标准的行级锁

  • 共享锁 (S lock)
  • 排他锁 (X lock)

简单理解,共享锁就是只读锁,排他锁就是读写锁。和java线程内的读写锁的兼容性是一样的。只有SS兼容,其他均不兼容。

2. 锁粒度

锁的申请是分级申请。表 -> 页 -> 行。在表或页上申请的锁被称为意向锁。意向锁也分共享锁和排他锁。

  • 共享意向锁 (IS lock)
  • 排他意向锁 (IX lock)

锁兼容性可概括为:意向锁之间相互兼容,S之间互相兼容。

通过锁粒度划分,有效减小或避免了表锁/页锁之间的等待。

3. 一致性非锁定读

一致性非锁定读指的是,通过快照,读取操作越过行上的锁读取数据。

然而,即便不申请锁,读取到的数据的一致性还是能得到保障的——在读取时,仍然只能读到事务提交后的状态的数据。

目的是为了提高并发读取的性能。

在不同的事物隔离级别上,快照的定义也不一样。read commit级别,快照是最新的其他线程的提交事务后的快照。repeatable read级别下,快照选取的是本事务开启时的快照。

在serialiable事务隔离级别下,InnoDB默认会为每条select语句加上lock in share mode,即不再支持一致性非锁定读了。

4. 一致性锁定读

读操作时,申请锁。语法层面上:

  • select ... for update; # 申请X锁
  • select ... lock in share mode; # 申请S锁

5. 自增长主键与锁

自增长主键生成时会申请锁,这个锁相当于一个表锁。但在实现上,为了提高性能,并不是等到事务提交才会释放,而是数据一旦插入即释放。

考虑事务回滚,自增长主键不一定是连续的。

 6. 锁算法

锁算法对于理解InnoDB解决幻读问题非常关键。锁算法分3种:

  • 行锁 (record lock)
  • 间隙锁(gap lock)
  • Next-Key Lock (行锁与间隙锁的综合,还有一种变种 Previous-Key Lock,区别是锁定的索引区间,一个是左开右闭,一个是左闭右开)。

间隙锁和Next-Key锁的本质就是,根据索引,把索引范围当锁定对象申请锁。这种思想让解决幻读(重复读)变得十分容易。

比如: select * from user where id between 10 and 20 for update; 锁定的对象至少是id在10到20之间的所有数据,所以当想要有另一个事务在10-20之间插入或删除数据时,将导致申请X锁失败,就不会发生幻读。

7. 锁升级

innoDB的锁使用bitmap算法,即对一个表上的数据按bitmap算法标记锁定还是非锁定,所以,其锁开销非常小,没必要当行锁多时升级为表锁。

8. 锁问题

通过锁解决了事务的隔离性,提高系统并发性能,但会有其他潜在问题。锁带来的问题被限制在三个问题上:脏读,幻读(不可重复读),写入丢失。

脏读,就是读到未提交的数据。read uncommited事务隔离级别下,会产生脏读。

幻读,在一个事务下两次读到不同的数据集。read commit事务隔离级别会读到幻读数据。而InnoDB的默认事务隔离级别repeatabl read下通过next-key lock算法可以避免幻读。

更新丢失,在当前数据库隔离级别上并不会出现数据库意义上的更新丢失,但是在应用程序级别会出现更新丢失。

9. 死锁

和大家熟悉的线程死锁问题一样。两个线程持有对方的要申请的锁就发生了死锁。

超时等待当然也可以解决死锁问题。

InnoDB采用一种更主动的方式——死锁检查线程来发现死锁。算法就是,建立一个事务之间的等待图,当图中发现环(回路)时,即发生死锁。

发生死锁后,根据操作权重,其中一个回退事务。

 

 posted on 2019-07-22 01:51  谷堆曲线  阅读(175)  评论(0编辑  收藏  举报