概览

表级锁和行级锁

MySQL 中的锁可以按照粒度分为锁定整个表的表级锁(table-level locking)和锁定数据行的行级锁(row-level locking):

  • 表级锁具有开销小、加锁快的特性;表级锁的锁定粒度较大,发生锁冲突的概率高,支持的并发度低;
  • 行级锁具有开销大,加锁慢的特性;行级锁的锁定粒度较小,发生锁冲突的概率低,支持的并发度高。

共享锁和排他锁

InnoDB 实现了以下两种类型的行锁

  • 共享锁(S):允许获得该锁的事务读取数据行(读锁),同时允许其他事务获得该数据行上的共享锁,并且阻止其他事务获得数据行上的排他锁。
  • 排他锁(X):允许获得该锁的事务更新或删除数据行(写锁),同时阻止其他事务取得该数据行上的共享锁和排他锁。
-- 获取共享锁
select ... for share

-- 获取排他锁
select ... for update

意向锁

在 mysql 中,当表级锁和行级锁同时存在时,比如事务 A 先获取了行级锁,事务 B 申请表锁(进行表结构修改),如果事务 B 申请成功,需要修改表中的数据会因为事务 A 获取了行级锁从而造成冲突。

InnoDB 引入了 意向锁,意向锁是一种表级锁,由 InnoDB 自动添加,不需要用户干预。意向锁也分为共享和排他两种方式:

  • 意向共享锁(IS):事务在给数据行加行级共享锁之前,必须先取得该表的 IS 锁。
  • 意向排他锁(IX):事务在给数据行加行级排他锁之前,必须先取得该表的 IX 锁。

务 A 必须先申请该表的意向共享锁,成功后再申请数据行的行锁。事务 B 申请表锁时,数据库查看该表是否已经被其他事务加上了表级锁;如果发现该表上存在意向共享锁,说明表中某些数据行上存在共享锁,事务 B 申请的写锁会被阻塞。

因此,意向锁是为了使得行锁和表锁能够共存,从而实现多粒度的锁机制

间隙锁

当使用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是是所谓的间隙锁(GAP锁)。

SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
-- 如果 id=10 的数据已经存在,其他事务不可以修改该条数据
-- 如果 id=15 的数据不存在,其他事务也不可以插入 id=15 的数据,因为(id 10-20)所有值之间的间隙是锁定的。

死锁

InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了InnoDB发生死锁是可能的。

发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并退回,另一个事务获得锁,继续完成事务。但 InnoDB 并不能完全自动检测到死锁,需要通过设置锁等待超时参数innodb_lock_wait_timeout来解决。

索引

索引失效的原因

todo

当同时命中多个索引时如何选择索引?

todo