🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] > 官网:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html 对于`UPDATE`、`DELETE`、`INSERT`语句,InnoDB会自动给涉及数据集加**排他锁(X)** 。而MyISAM在执行查询语句`SELECT`前,会自动给涉及的所有表加**读锁**,在执行增、删、改操作前,会自动给涉及的表加**写锁**,这个过程并不需要我们去手动操作。 :-: ![](https://img.kancloud.cn/71/17/7117af80f7ae6eece9361b5d9590776a_803x314.png) ## 表锁 **开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低。** 不同的存储引擎支持的锁粒度是不一样的: * **InnoDB行锁和表锁都支持、MyISAM只支持表锁** * **InnoDB只有通过索引条件检索数据才使用行级锁,否则,InnoDB使用表锁也就是说,InnoDB的行锁是基于索引的** 表锁下又分为两种模式: 表读锁(Table Read Lock)、表写锁(Table Write Lock)。 * **读读不阻塞:** 当前用户在读数据,其他的用户也在读数据,不会加锁 * **读写阻塞:** 当前用户在读数据,其他的用户不能修改当前用户读的数据,会加锁 * **写写阻塞:** 当前用户在修改数据,其他的用户不能修改当前用户正在修改的数据,会加锁 读锁和写锁是互斥的,读写操作是串行 * 如果某个进程想要获取读锁,同时另外一个进程想要获取写锁。在mysql中,写锁是优先于读锁的! * 写锁和读锁优先级的问题是可以通过参数调节的:`max_write_lock_count`和`low-priority-updates` > MyISAM支持查询与插入操作的并发进行,也可以通过系统变量`concurrent_insert`指定哪种模式。在MyISAM中默认:如果MyISAM表的中间没有被删除的行的话,那MyISAM是允许在一个进程读表的同时,另一个进程从表尾做插入记录的。但是INNODB是不支持的。 ## 行锁 **开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低。** InnoDB实现了两种类型的行锁: * **共享锁(S锁、读锁)**: 允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。即多个客户可以同时读取同一个资源,但不允许其他客户修改。 * **排他锁(X锁、写锁)**: 允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的读锁和写锁。写锁是排他的,写锁会阻塞其他的写锁和读锁。 **为了允许行锁和表锁共存,实现多粒度锁机制**,InnoDB还有两种内部使用的**意向锁(Intention Locks)**,这两种意向锁都是**表锁**: * **意向共享锁(IS):** 事务打算给表数据行加共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。 * **意向排他锁(IX):** 事务打算给表数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。 | | X | IX | S | IS | | --- | --- | --- | --- | --- | | X | 冲突 | 冲突 | 冲突 | 冲突 | | IX | 冲突 | 兼容的 | 冲突 | 兼容的 | | S | 冲突 | 冲突 | 兼容的 | 兼容的 | | IS | 冲突 | 兼容的 | 兼容的 | 兼容的 | > 意图锁不会阻塞任何东西。意图锁的主要目的是表明有人正在锁定一行,或者要锁定表中的一行。 ## 悲观锁、乐观锁 ### 并发控制 当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。这就叫做并发控制。并发控制的目的是**保证一个用户的工作不会对另一个用户的工作产生不合理的影响。** 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。 实现并发控制的主要手段分为乐观并发控制和悲观并发控制两种。 ### 悲观锁 悲观并发控制(又名 “悲观锁”,Pessimistic Concurrency Control,缩写 “PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作的某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。 **实现:** 在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。 其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。 #### 优缺点 悲观并发控制实际上是 “先取锁再访问” 的保守策略,为数据处理的安全提供了保证。 一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数,降低了并行性 ### 乐观锁 乐观并发控制(又名 “乐观锁”,Optimistic Concurrency Control,缩写 “OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。 相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。 数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。 #### 优缺点 乐观并发控制相信事务之间的数据竞争 (data race) 的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。 ## 间隙锁 (Gap Locks) 间隙锁是在**索引记录之间的间隙**上的锁,或在第一条索引记录之前或最后一条索引记录之后的间隙上的锁。 ~~~ SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE ~~~ 阻止其他事务将值`15`插入 column`t.c1`,无论该列中是否已经存在任何此类值,因为该范围内所有现有值之间的间隙都已锁定。 > 不同的事务可以在间隙上持有冲突的锁。 唯一目的是防止其他事务插入到间隙中。间隙锁可以共存。一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。 **间隙锁避免了在 Repeatable read隔离级别下的幻读** ## 记录锁 (Record Locks) 对索引记录的锁。例如,`SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;`阻止任何其他事务插入、更新或删除值为 的`t.c1`行`10`。 记录锁总是锁定索引记录,即使定义的表没有索引。对于这种情况,`InnoDB`创建一个隐藏的聚集索引并将该索引用于记录锁定 ## 下一键锁 (Next-Key Locks) 下一个键锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。 `InnoDB`执行行级锁定的方式是,当它搜索或扫描表索引时,它会在它遇到的索引记录上设置共享或排他锁。因此,行级锁实际上是索引记录锁。索引记录上的 **next-key** 锁定也会影响该索引记录之前的“gap”。也就是说,**next-key** 锁是索引记录锁加上索引记录前面的间隙上的间隙锁。 ## 死锁 两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。 死锁的关键在于:**两个(或以上)的 Session 加锁的顺序不一致。** ## InnoDB中不同SQL语句设置的锁 1. `SELECT ... FROM` 是一致的读取,读取数据库的快照,并且**不设置锁**,除非事务隔离级别设置为`SERIALIZABLE`。 2. `SELECT ... FROM ... LOCK IN SHARE MODE` 在所有索引扫描范围的索引记录上加上共享的next key锁;如果是唯一索引,只需要在相应记录上加index record lock。这些被共享lock住的行无法进行`update`、`delete`。 如果没有使用到索引,则锁住全表(表级的排他锁),无法进行`insert`、`update`、`delete`。 3. `SELECT ... FROM ... FOR UPDATE` 在所有索引扫描范围的索引记录上加上**排他的next key锁**。如果是唯一索引,只需要在相应记录上加`index record lock`。 如果没有利用到索引将锁住全表(表级的排他锁),其它事务无法进行`insert`、`update`、`delete`操作。 4. `UPDATE ... WHERE ...` 在所有索引扫描范围的索引记录上加上**排他的next key锁**。如果是唯一索引,只需要在相应记录上加`index record lock`。 如果没有利用到索引将锁住全表(表级的排他锁),其它事务无法进行其他的`insert`、`update`、`delete`操作。 5. `DELETE FROM ... WHERE ...` 在所有索引扫描范围的索引记录上加上**排他的next key锁**。如果是唯一索引,只需要在相应记录上加`index record lock`。 如果没有利用到索引将锁住全表(表级的排他锁),其它事务无法进行其它的`insert`、`update`、`delete`操作 6. `INSERT……` 在插入的记录上加一把排他锁,这个锁是一个`index-record lock`,并不是**next-key 锁**,因此就没有**gap 锁**,他将不会阻止其他会话在该条记录之前的gap插入记录。