MySQL锁机制
mysql锁的分类:
- 按照锁粒度分类:行锁、表锁、页锁
- 按照锁使用方式:共享锁,排他锁
- 按照锁的思想:悲观锁、乐观锁
- 行锁类型:Record Lock、Gap Lock、Next-key Lock
锁粒度
1. 行锁
行锁是锁粒度最细的锁,表示只针对当前操作的行进行加锁。行锁有可能会出出现死锁的情况,行锁按照使用方式分为共享锁和排他锁;InnoDB默认使用行锁;
-
共享锁(S锁,读锁)
当事务A对数据加上了S锁,事务A可以读数据但不能修改数据,其他事务也只能对数据加S锁,不能加其它锁,知道事务A释放锁;保证了其他事务可以读数据,但在S锁中不能修改数据。
-
排他锁(X锁,写锁)
排它锁,也称作独占锁,一个锁在某一时刻只能被一个线程占有,其它线程必须等待锁被释放之后才可能获取到锁;
当事务A对数据加上X锁,事务A可以读也可以写数据,其他事务不能对数据加任何锁,直到事务A释放锁。保证了其他事务在加了X锁中的数据不能进行读写。
2. 表锁
表锁是锁粒度最大的锁,表示对整张表加锁,不会出现死锁的情况,但是很有可能造成锁冲突,
表锁也分为共享锁和排他锁,意义和行锁一样;
3. 页锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
锁思想
无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。
1. 悲观锁
悲观锁是一种并发控制的方法,它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作对某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。
执行流程: 在对任意记录进行修改之前,先尝试为该记录加上X锁,如果加锁失败,说明该记录正在被修改,那么当前查询就要进行等待或者抛出异常;
特点: 悲观锁采取了先获取锁在访问的策略,保证了数据的一致性,但是在效率上降低了并发度,增加了死锁的机会;
2. 乐观锁
乐观锁也是一种并发控制的方法,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。
执行流程: 在修改某个数据时,为数据增加一个版本标识(也就是数据版本),当读取数据时,将版本标识的值一起读出来,数据每次更新,则对版本表示进行更新,当我们提交更新时,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识对比,如果两个版本标识相同,则更新,否则认为是过期数据;
特点: 乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。
行锁类型
Record Lock 记录锁
单条索引上加锁,record lock 永远锁的是索引,而非数据本身,如果innodb表中没有索引,那么会自动创建一个隐藏的聚集索引,锁住的就是这个聚集索引。
Gap Lock 间隙锁
对符合条件范围的“间隙”加锁,锁定记录的范围,不包含索引项本身。其他事务不能在锁范围内插入数据。间隙(GAP)是指 键值在条件范围内但并不存在的记录。
Next-key Lock 临键锁
锁定索引项本身和间隙。Record Lock和Gap Lock的结合,Next-key Lock就是我们所说的间隙锁,可解决幻读问题。
InnoDB锁特性
- InnoDB 在 可重复读隔离级别下使用的是Next-Key Lock 锁,解决的幻读问题,因此InnoDB 引擎的默认的隔离级别 REPEATABLE-READ(可重读) 已经达到了SQL标准的 SERIALIZABLE(可串行化) 隔离级别,并且事务不需要串行化。
- 当查询的索引含有唯一属性时,将next-key lock降级为record key。
- 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论 是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
- Innodb中行级锁是加在索引上,所以只有使用索引时,才会加行锁,否则只会加表锁。 但这并不意味着,只要使用了索引就会加行级锁,如果MySQL认为全表扫描效率更高,比如对一些很小的表或者索引范围包括大部分表数据,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。
事务的ACID是通过InnoDB日志和锁来保证。事务的隔离性是通过数据库锁的机制实现的,持久性通过redo log(重做日志)来实现,原子性和一致性通过Undo log(回撤日志)来实现。Undo Log的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行了roll back语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。 和Undo Log相反,Redo Log记录的是新数据的备份。在事务提交前,只要将RedoLog持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化。系统可以根据Redo Log的内容,将所有数据恢复到最新的状态。
死锁
当两个事务都需要获得对方持有的锁,导致双方都在等待,这就产生了死锁。发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个则可以获取锁完成事务;
在InnoDB中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。当两个事务同时执行,一个锁住了主键索引,在等待其他相关索引。另一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。
避免死锁:
- 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,降低死锁机会。
- 在同一事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
- 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
相关文章:
https://blog.csdn.net/qq_38238296/article/details/88362999
https://blog.csdn.net/fuzhongmin05/article/details/91126936
|