# MySQL中锁概述

## 为什么需要锁

在MySQL默认的隔离级别（可重复读）下，已经不存在脏读、不可重复读的问题，并且InnoDB存储引擎通过多版本并发控制（MVCC，Multiversion Concurrency Control）机制解决了幻读的问题。

那为什么还需要锁机制呢？

来看这种情况：当两个或多个事务选择同一行，然后基于最初选定的值并发更新该行时，由于每个事务都不知道其他事务的存在，就会发生**更新丢失问题——最后的更新覆盖了其他事务所做的更新**。 所以对于更新丢失这种问题，并不能单靠数据库事务控制器来解决，需要对要更新的数据加必要的锁来解决。

## InnoDB中锁的分类

我们知道，InnoDB存储引擎的两个重要特性就是事务管理和行级锁。在MyISAM存储引擎中，只有表锁。这里的表锁和行锁的区别，是指加锁的对象的不同，表锁是对整个表加锁，行锁是对一行或者多行记录进行加锁。行级锁的锁定粒度小，发生锁冲突的概率最低，并发度也最高。

在具体阐述InnoDB中的行锁之前，先将InnoDB中所有锁的类型列出来，然后按照顺序进行解释。

**表锁**

* 自增锁(Auto-inc Locks)
* 意向锁(Intention Locks)
* * 意向共享锁
  * 意向排他锁

**行级锁**

* 共享锁
* 排它锁
* 间隙锁(Gap Locks)
* 插入意向锁(Insert Intention Locks)

### 1、共享锁和排它锁

先看定义：

共享锁（S）

* * 事务拿到某一行记录的共享锁，才可以读取这一行；
  * 事务拿到共享锁后，可以去读加锁的行，同时会阻止其他事务获得相同记录集的排他锁，但其他事务可以同时获取相同记录集的共享锁。

排他锁（Ｘ）

* * 事务拿到某一行记录的排它X锁，才可以修改或者删除这一行；
  * 事务拿到排它锁后，可以进行更新操作（update和delete），同时会阻止其他事务取得相同的记录集共享读锁和排他写锁。

最常见的使用共享锁和排它锁的场景：

共享锁：SELECT ... LOCK IN SHARE MODE;

排它锁：SELECT ... FOR UPDATE;

### 2、意向共享锁和意向排它锁

为了允许行锁和表锁共存，实现多粒度锁机制，InnoDB还有两种内部使用的意向锁（`Intention Locks`），这两种意向锁都是表锁。

* 意向共享锁（IS）：事务打算给记录行共享锁，**事务在给一个记录行加共享锁（S）前必须先取得该表的IS锁。**
* 意向排他锁（IX）：事务打算给记录行加排他锁，**事务在给一个记录行加排他锁（X）前必须先取得该表的IX锁。**

意向锁是InnoDB自动加的，不需要用户干预。

**意向锁的作用**：解决表级锁和行级锁之间的冲突，比如事务A对表`table1`中的某条记录加了共享锁，让这一行只能读，不能写。此时，又有事务B申请`table1`的表锁。如果事务B申请成功，那么理论上它就能修改`table1`中的任意一行，这与A持有的行锁是冲突的。数据库需要避免这种冲突，就是说要让B的申请被阻塞，直到A释放了行锁。

### 3、间隙锁

当我们用范围条件而不是相等条件检索数据来请求共享或排他锁时，InnoDB除了会给符合条件的已有记录的索引项加锁，还会对键值在条件范围内但并不存在的记录进行加锁，这种锁机制就是所谓的间隙锁。

举例来说，假如`table`表中只有101条记录，其`id`的值分别是`1,2,...,100,101`，那么如下SQL：

```sql
SELECT * FROM table WHERE id > 100 FOR UPDATE
```

上述SQL中，是一个范围条件的检索，InnoDB不仅会对符合条件的id值为101的记录加锁，也会对id大于101（这些记录并不存在）的“间隙”加锁。InnoDB使用间隙锁的目的，一方面是为了防止幻读，以满足相关隔离级别的要求，对于上面的例子，如果不使用间隙锁，如果其他事务插入了id大于100的任何记录，那么本事务如果再次执行上述语句，就会发生幻读；另一方面，是为了满足其恢复和复制的需要（TODO）。\
很显然，在使用范围条件检索并锁定记录时，InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入，这往往会造成严重的锁等待。因此，在实际开发中，尤其是并发插入比较多的应用，我们要尽量优化业务逻辑，尽量使用相等条件来访问更新数据，避免使用范围条件。

### 4、自增锁和插入意向锁

这是两种比较特殊的锁，仅仅在insert的时候会出现。

**自增锁**

自增锁是一种特殊的表级别锁（table-level lock），专门针对事务插入AUTO\_INCREMENT类型的列。如果一个事务正在往表中插入记录，所有其他事务的插入必须等待，以便第一个事务插入的行，是连续的主键值。

**插入意向锁**

对已有数据行的**修改与删除**，必须加排它锁来保证安全，但对于**数据的插入**，是否还需要加这么强的锁，来实施互斥呢？插入意向锁，孕育而生。插入意向锁是间隙锁(Gap Locks)的一种：多个事务，在同一个索引，同一个范围区间插入记录时，如果插入的位置不冲突，不会彼此阻塞。

举个例子，MySQL，InnoDB，默认的隔离级别(RR)，假设有数据表：t(id PrimaryKey, name)，数据表中有数据：（10, wangwu），（20, zhangsan），（30, lisi），此时：

事务A先执行，还未提交：`insert into t values(11, xxx);`

事务B后执行：`insert into t values(12, ooo);`

此时，事务B会不会被阻塞？虽然事务隔离级别是RR，虽然是同一个索引，虽然是同一个区间，但插入的记录并不冲突，所以并不会阻塞事务B。

但假如上面的id列设置了AUTO\_INCREMENT，为了保证同一事务中的insert的id自增特性，会使用自增锁，此时，将会阻塞事务B。

### 其他说明

（1）InnoDB中的行锁是通过给索引上的索引项加锁来实现的，只有通过索引条件检索数据，InnoDB才会使用行级锁，否则，InnoDB将使用表锁；即便在条件中使用了索引字段，但如果MySQL认为全表扫描效率更高，InnoDB将使用表锁，而不是行锁。因此，在分析锁冲突时，别忘了检查SQL的执行计划，以确认是否真正使用了索引。

（2）什么时候使用锁

对于`UPDATE、DELETE`和`INSERT`语句，InnoDB会自动给涉及的记录集加排他锁；

对于普通`SELECT`语句，InnoDB不会任何锁，除非通过下述两种方式显式地加锁：

```sql
# 加共享锁
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
# 加排它锁
SELECT * FROM table_name WHERE ... FOR UPDATE
```

由（1）可知，并不是`UPDATE、DELETE`和`INSERT`语句，就一定是行锁，也有可能是表锁。

（3）死锁的例子：比如，事务A和事务B同时对记录a和记录b进行更新，事务A先对记录a更新（更新操作时InnoDB会对该行自动加锁），然后尝试对记录b更新，事务B先对记录b更新（假如与事务A更新记录a同时进行），然后尝试对记录a更新。此时，两个事务都等待对方释放锁，同时又持有对方需要的锁，则陷入死循环。

## 参考

[MySQL中的锁（表锁、行锁）](http://www.cnblogs.com/chenqionghe/p/4845693.html)\
[MySQL优化系列（八）--锁机制超详细解析（锁分类、事务并发、引擎并发控制）](http://blog.csdn.net/jack__frost/article/details/73347688)\
[MySQL innodb中各种SQL语句加锁分析](http://www.fordba.com/locks-set-by-different-sql-statements-in-innodb.html)\
[MySQL锁详解](http://www.cnblogs.com/luyucheng/p/6297752.html)\
[挖坑，InnoDB的七种锁](https://mp.weixin.qq.com/s/IE31GSDP0Ndjzc8kFj4Ukw)

**扩展阅读**

[MySQL 加锁处理分析](http://hedengcheng.com/?p=771)

[MySQL中的锁（表锁、行锁，共享锁，排它锁，间隙锁）](https://blog.csdn.net/soonfly/article/details/70238902)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://maxwell.gitbook.io/way-to-architect/shu-ju-ku/mysql/mysqlsuo/mysqlzhong-suo-de-fen-lei.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
