Skip to content

MySQL锁

锁是计算机在执行多线程或线程时用于并发访问同一共享资源时的同步机制,MySQL中的锁是在服务器层或者存储引擎层实现的,保证了数据访问的一致性与有效性。

MySQL锁:

  • 按模式分类为: 乐观锁与悲观锁。
  • 按粒度分可以分为: 全局锁、表级锁、页级锁、行级锁。
  • 按属性可以分为: 共享锁、排它锁。
  • 按状态分为: 意向共享锁、意向排它锁。
  • 按算法分为: 间隙锁、临键锁、记录锁。

全局锁

全局锁就是对整个数据库实例加锁。

应用场景: 全库逻辑备份(mysqldump)

实现方式:

MySQL 提供了一个加全局读锁的方法,命令是Flush tables with read lock (FTWRL)。 当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。

缺点:

  • 如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就能停止。
  • 如果在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。

解决方法:

mysqldump使用参数--single-transaction,启动一个事务,确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的。

表级锁

当前操作的整张表加锁,最常使用的 MyISAM 与 InnoDB 都支持表级锁定

MySQL 里面表级别的锁有两种:

  1. 表锁 lock tables ... read/write
  2. 元数据锁(meta data lock,MDL)。MDL 不需要显式使用,在访问一个表的时候会被自动加上,在 MySQL 5.5 版本中引入了 MDL,当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加MDL写锁

缺点: 给一个表加字段,或者修改字段,或者加索引,需要扫描全表的数据。只要操作表,就要小心对线上服务造成影响,实际上是非常容易造成影响

行级锁

行级锁是粒度最低的锁,发生锁冲突的概率也最低、并发度最高。但是加锁慢、开销大,容易发生死锁现象。

MySQL中只有InnoDB支持行级锁,行级锁分为共享锁和排他锁。

行级锁实现方式

在MySQL中,行级锁并不是直接锁记录,而是锁索引

索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。

在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key lock 临键锁.

排他锁(有在业务中使用)

排它锁,又称之为写锁,简称X锁(Exclusive),当事务对数据加上写锁后,其他事务既不能对该数据添加读写,也不能对该数据添加写锁,写锁与其他锁都是互斥的。只有当前数据写锁被释放后,其他事务才能对其添加写锁或者是读锁。 MySQL InnoDB引擎默认update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型。

应用场景: 写锁主要是为了解决在修改数据时,不允许其他事务对当前数据进行修改和读取操作,从而可以有效避免”脏读”问题的产生。

实现方式: select ... for update

共享锁

共享锁,又称之为读锁,简称S锁(SHARE),当事务A对数据加上读锁后,其他事务只能对该数据加读锁,不能做任何修改操作,也就是不能添加写锁。只有当事务A上的读锁被释放后,其他事务才能对其添加写锁。

应用场景: 共享锁主要是为了支持并发的读取数据而出现的,读取数据时,不允许其他事务对当前数据进行修改操作,从而避免”不可重读”的问题的出现。

适合于两张表存在关系时的写操作。 例如parent表和child表,这类的业务类型

实现方式: select ... lock in share mode

页级锁

页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。因此,采取了折衷的页级锁,一次锁定相邻的一组记录

MySQL乐观锁

一般使用数据版本(Version)记录机制实现,在数据库表中增加一个数字类型的“version”字段来实现

MySQL悲观锁

select ... for update 是MySQL提供的实现悲观锁的方式,属于排它锁。可以保证当前的数据不会被其它事务修改

意向共享锁和意向排它锁

解释: 意向锁是表锁,为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。

作用: 当有事务A有行锁时,MySQL会自动为该表添加意向锁,事务B如果想申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁,而直接判断是否存在意向锁,增强性能。

间隙锁、临键锁、记录锁

解释: 记录锁、间隙锁、临键锁都是排它锁,而记录锁的使用方法跟排它锁一致。

记录锁

记录锁是封锁记录,记录锁也叫行锁。 select ... for update

间隙锁

间隙锁基于非唯一索引,它锁定一段范围内的索引记录。使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。

1. 什么是间隙锁:当我们采用范围条件查询数据时,InnoDB 会对这个范围内的数据进行加锁。比如有 id 为: 1、3、5、7 的 4 条数据,我们查找 1-7 范围的数据。那么 1-7 都会被加上锁。2、4、6 也在 1-7 的范围中,但是不存在这些数据记录,这些 2、4、6 就被称为间隙。

2. 间隙锁的危害:范围查找时,会把整个范围的数据全部锁定住,即便这个范围内不存在的一些数据,也会被无辜的锁定住,比如我要在 1、3、5、7 中插入 2,这个时候 1-7 都被锁定住了,根本无法插入 2。在某些场景下会对性能产生很大的影响

3. 间隙锁可以解决幻读

举例: 表中有一个范围 id 为(3,5)间隙锁,那么其他事务就无法插入 id = 4 这条记录了,这样就有效的防止幻读现象的发生。

临键锁

临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间,是一个左开右闭区间。临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。

每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

举例: 表中有一个范围 id 为(3,5] 的 next-key lock,那么其他事务即不能插入 id = 4 记录,也不能修改 id = 5 这条记录