分类
MySQL中的锁是在服务器层或者存储引擎层实现的,保证了数据访问的一致性与有效性。
全局锁、表级锁、页级锁、行级锁
全局锁
- 对整个数据库实例加锁
- 应用场景:全库逻辑备份
- 实现:Flush tables with read lock (FTWRL)
表级锁
- 当前操作的整张表加锁,MyISAM 与 InnoDB 都支持表级锁定
- 有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)
- 实现:lock tables … read/write
- 注意:需要在低峰期做ddl 变更
页级锁
- 表级锁速度快,但冲突多,行级冲突少,但速度慢
- 因此,采取了折衷的页级锁,一次锁定相邻的一组记录
- BDB 引擎支持页级锁
行级锁
- 行级锁是粒度最低的锁,发生锁冲突的概率也最低、并发度最高,但是加锁慢、开销大,容易发生死锁现象
- InnoDB默认为行级锁,行级锁分为共享锁和排他锁
- 实现:
- 行级锁并不是直接锁记录,而是锁索引,索引分为主键索引和非主键索引两种
- 如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引
- 如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引
- 在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking
乐观锁和悲观锁
乐观锁和悲观锁并不是锁,而是锁的设计思想
乐观锁
乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测
如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做
- 应用:适用于读多写少
- 实现:采用版本号机制或者时间戳机制实现
- 版本号机制
- 在表中设计一个版本字段 version
- 第一次读的时候,会获取 version 字段的取值
- 对数据进行更新或删除操作时,会执行UPDATE … SET version=version+1 WHERE version=version
- 如果已经有事务对这条数据进行了更改,修改就不会成功
- 时间戳机制
- 与版本号机制一样,也是在更新提交的时候,将当前数据的时间戳和更新之前取得的时间戳进行比较
- 如果两者一致则更新成功,否则就是版本冲突
悲观锁
对数据被其他事务的修改持保守态度,会通过数据库自身的锁机制来实现,从而保证数据操作的排它性。
- 应用:适合写多读少、并发量不大、数据一致性比较高
- 实现:关闭MySQL的自动提交,set autocommit=0
- 共享锁和排它锁是悲观锁不同的实现
共享锁、排它锁
共享锁
- 共享锁,又称之为读锁,简称S锁
- 多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改- 当事务A对数据加上读锁后,其他事务只能对该数据加读锁,不能做任何修改操作,也就是不能添加写锁- 只有当事务A上的读锁被释放后,其他事务才能对其添加写锁,从而避免"不可重读"问题的出现
- 应用:适合于两张表存在关系时的写操作
- 实现:select …lock in share mode
排它锁
- 排它锁,又称之为写锁,简称X锁
- 当事务对数据加上写锁后,其他事务既不能对该数据添加读锁,也不能对该数据添加写锁,写锁与其他锁都是互斥的注:InnoDB引擎默认update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型
- 应用:为了解决在修改数据时,不允许其他事务对当前数据进行修改和读取操作,从而可以有效避免”脏读”问题的产生
- 实现:select …for update
意向共享锁、意向排它锁
- 意向锁是表锁,为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存- 当事务A有行锁时,MySQL会自动为该表添加意向锁,事务B如果想申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁,而直接判断是否存在意向锁,增强性能注:排它/共享锁指的都是表锁, 意向锁不会与行级的共享/排它锁互斥
间隙锁、临键锁、记录锁
记录锁、间隙锁、临键锁都是排它锁
记录锁
- 记录锁是封锁记录,记录锁也叫行锁
间隙锁
- 间隙锁基于非唯一索引,它锁定一段范围内的索引记录
- 使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据
临键锁
- 临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间,是一个左开右闭区间
- 临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效
- 注:InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁