mysql 常见锁的类型(一)

@[toc]

一、锁的分类

1.1 加锁的目的

当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况,若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。加锁的目的其实就是保证多用户环境下保证数据库完整性和一致性。

1.2 锁的类别

  • 基于加锁机制分类:乐观锁悲观锁
  • 基于锁的属性分类:共享锁排他锁
  • 基于锁的粒度分类:行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎)
  • 基于锁的模式分类:记录锁、间隙锁、临键锁、意向共享锁、意向排他锁

二、乐观锁悲观锁

2.1. 乐观锁

  • 乐观锁假设数据不会发生冲突,只有在数据提交更新时才会检测是否冲突,如果冲突了才会返回错误信息。
  • 使用乐观锁,如果发现冲突了,则返回错误信息给用户,让用户自已决定如何操作。
  • 乐观锁的实现不依靠数据库提供的锁机制,需要我们自已实现,实现方式一般是记录数据版本,一种是通过版本号控制,一种是通过时间戳控制。原理如下:
  1. 给表加一个版本号或时间戳的字段,读取数据时,将版本号一同读出,数据更新时,将版本号加1。
  2. 当我们提交数据更新时,版本号会作为where条件,来判断当前库里的版本号与第一次读取出来的版本号是否相等。如果相等,则予以更新,否则认为数据过期,拒绝更新,让用户重新操作。
  • 乐观锁是基于程序实现的,所以不存在死锁的情况,适用于读多写少的应用场景。如果经常发生冲突,上层应用不断的让用户进行重新操作,这反而降低了性能,这种情况下悲观锁就比较适用。

2.2. 悲观锁:

  • 悲观锁总认为会发生并发冲突,获取和修改数据时,别人会修改数据。所以在整个数据处理过程中,需要将数据锁定,具有强烈的独占和排他特性。
  • 悲观锁通常依靠数据库提供的锁机制实现,Mysql中的共享锁和排他锁都属于悲观锁。
  • 适用于并发量不太大、写入比较频繁、数据一致性比较高的场景。

三、共享锁与排他锁

InnoDB 实现了标准的行级锁,包括两种:共享锁(简称 s 锁)、排它锁(简称 x 锁)

  • 共享锁(S锁):允许不同事务之间加共享锁读取,但不允许其它事务修改或者加入排他锁,如:SELECT ... LOCK IN SHARE MODE。
  • 共享锁的特性是会阻塞其他事务对其插入、更新、删除。主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。
  • 排他锁(X锁):当一个事务加入排他锁后,不允许其他事务加共享锁或者排它锁读取,更加不允许其他事务修改加锁的行。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取,避免了出现脏数据和脏读的问题
  • mysql 常见锁的类型(一)

  • 操作类型加锁机制:对于 UPDATE 、DELETE 和 INSERT 语句,InnoDB会自动给涉及的数据集增加排他锁(X)。对于普通的 SELECT 语句,InnoDB不会加任何锁,但事务也可以通过以下语句显式的给SELECT语句加共享锁和排他锁,分别如下:
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 
SELECT * FROM table_name WHERE ... FOR UPDATE
  • 共享锁和排他锁的兼容性

例如,如果事务 T1 持有行 r 的 s 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:

  1. T2 请求 s 锁立即被允许,结果 T1 T2 都持有 r 行的 s 锁
  2. T2 请求 x 锁不能被立即允许
  3. 如果 T1 持有 r 的 x 锁,那么 T2 请求 r 的 x、s 锁都不能被立即允许,T2 必须等待T1释放 x 锁才可以,因为X锁与任何的锁都不兼容。
    ![S锁和X锁兼容性](​​https://img-blog.csdnimg.cn/88fbf432837044c480ff9758214d0542.png#pic_center​​ =600x)

四、表锁

  • 表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问;
  • 特点: 粒度大,加锁简单,容易冲突;

五、意向锁

  • 意向锁的解释:当一个事务试图对整个表进行加锁(共享锁或排它锁)之前,首先需要获得对应类型的意向锁(意向共享锁或意向排他锁)。
  • 意向共享锁( IS 锁):当一个事务试图对整个表经行加共享锁之前,首先需要获得这个表的意向共享锁。
  • 意向排他锁( IX 锁):当一个事务试图对整个表经行加排他锁之前,首先需要获取这个表的意向排他锁。
  • 意向锁的作用:如果一个事务试图添加表级别的共享锁或排它锁,则需要检查表的各个页或行是否有锁,这样效率非常低下。意向锁是表示对某一个表进行加锁的状态,此时只需检查表上的意向锁即可。意向锁是 InnoDB 自动加的,不需要用户干预
  • 举例如下:
  1. 事务A对user_info表执行一个SQL:update user_info set name =”张三” where id=6 加锁情况如下图:
    此时,是对"id=6"记录加的行锁。
  2. 与此同时数据库又接收到事务B修改数据的请求:SQL: update user_info set name ='李四';
  3. 因为事务B是对整个表进行修改操作,那么此SQL是需要对整个表进行加排它锁的(update加锁类型为排他锁);
  4. 我们首先做的第一件事是先检查这个表有没有被别的事务锁住,只要有事务对表里的任何一行数据加了共享锁或排他锁我们就无法对整个表加锁(排他锁不能与任何属性的锁兼容)。
  5. 因为INNODB锁的机制是基于行锁,那么这个时候我们会对整个索引每个节点一个个检查。检查每个节点是否被别的事务加了共享锁或排它锁。
  6. 最后检查到索引ID为6的节点被事务A锁住了,最后导致事务B只能等待事务A锁的释放才能进行加锁操作。
  • 意向锁的思考:在A事务的操作过程中,后面的每个需要对user_info加持表锁的事务都需要遍历整个索引树才能知道自己是否能够进行加锁,这种方式是不是太浪费时间和损耗数据库性能了?
  • 所以就有了意向锁的概念:如果当事务A加锁成功之后就设置一个状态告诉后面的人,已经有人对表里的行加了一个排他锁了,你们不能对整个表加共享锁或排它锁了,那么后面需要对整个表加锁的人只需要获取这个状态就知道自己是不是可以对表加锁,避免了对整个索引树的每个节点扫描是否加锁,而这个状态就是我们的意向锁
  • 意向锁和其他锁的兼容性
    InnoDB存储引擎中锁的兼容性如下表:
    ![InnoDB存储引擎中锁的兼容性](​​https://img-blog.csdnimg.cn/80215228ad7f4fe7a52cf8c04222200c.jpeg#pic_center​​ =700x)
  • 插入意向锁(Insert Intention)
  • 插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,亦即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。
  • 插入意向锁, 一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了所谓的gap锁,如果有的话,插入操作需要等待,直到拥有gap锁的那个事务提交。InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构,表明有事务想在某个间隙中插入新记录,但是现在在等待。
  • 插入意向锁和间隙锁互斥。插入意向锁互相不互斥。
  • 事务数据类似于下面:
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000066; asc f;;
1: len 6; hex 000000002215; asc " ;;
2: len 7; hex 9000000172011c; asc r ;;...

六、行级锁

  • 行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的记录不能访问,其他的记录可正常访问。
  • 在MySQL的InnoDB中,行级锁并不是直接锁记录,行锁是通过给索引项加锁实现的,如果没有索引,InnoDB会通过隐藏的聚簇索引来对记录加锁。
  • 特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高。
  • 索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引
  • 也就是说:如果不通过索引条件检索数据,比如update时,where条件没有走索引,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样,在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能
  • 行锁分为三种情形:Record lock、Gap lock、Next-key Lock。

七、记录锁(Record Locks)

  • 记录锁也属于行锁中的一种,是最简单的行锁,事务在加锁后锁住的仅仅只是表的某一条记录。
  • 记录锁并不是锁住该条记录,永远都是加在索引上的,即使一个表没有索引,InnoDB也会隐式的创建一个索引,并使用这个索引实施记录锁。如: 记录排他锁(x):SELECT * FROM t WHERE id = 10 FOR UPDATE;记录读锁(s):SELECT * FROM t WHERE id = 10 LOCK IN SHARE MODE。
  • 触发条件:精准条件命中,并且命中的条件字段是唯一索引。例如:update user_info set name=’张三’ where id=1 ,这里的id是唯一索引。
  • 加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题(s),也避免了在修改的事务未提交前被其他事务读取的脏读问题(x)。
  • 记录锁的事务数据(关键词:lock_mode X locks rec but not gap),记录如下:
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 8000000a; asc ;;
1: len 6; hex 00000000274f; asc 'O;;
2: len 7; hex b60000019d0110; asc ;;