跳过正文
  1. 技术文档/
  2. MySQL/
  3. 高级/

概述
#

并发事务访问
#

并发事务访问的情况
#

读 - 读
#

并发读,不需要锁

写 - 写
#

并发写,会出现脏写问题,而脏写问题不能容忍

解决:先到的事务需加锁,后到的事务需等待,直到锁释放再去加锁执行

加锁时,创建锁结构,会关联事务 id 等

读 - 写 或 写 - 读
#

一个读,一个写,可能发生脏读、不可重复度、幻读问题

并发问题的解决方案
#

方案一:读操作利用多版本并发控制(MVCC),写操作加锁
#

方案二:读写操作都采用加锁的方式
#

方案一、二简单对比
#

锁不同角度分类
#

从数据操作的类型划分
#

共享锁排他锁

获取了 S 锁后,其他事务可以继续获得 S 锁,但不能获得 X 锁除非 S 锁已释放

获取了 X 锁后,其他事务不能继续获得 S/X 锁

读操作
#

要么获取 S 锁,要么获取 X 锁(有些场景的读操作也需要排他性)

获取共享锁的方式读

Select * from tb_user lock in share mode;

Select * from tb_user for share; (8.0 版本)

获取排他锁的方式读

Select * from tb_user for update;

写操作
#

包含 insert、delete、update 操作

Delete 实际上是加 X 锁修改标记

Update 有 3 种情况:

  1. 修改主键字段,delete + insert,加 X 锁和隐式锁;
  2. 修改非主键字段,且字段数据占用存储不变,加 X 锁;
  3. 修改非主键字段,且字段数据占用存储有变,delete + insert,加 X 锁和隐式锁;

Insert 实际上是加隐式锁

从数据操作的粒度划分
#

表锁
#

表级别的 S 锁和 X 锁
#

查看表加的锁

Show open tables

手动加锁

Lock tables tb_user [read | write]

Myisam 下的表级 S 锁和 X 锁
#

意向锁 Intention Lock
#

意向锁要解决的问题
#

考虑一个场景:事物 A 对某一行记录添加了排他锁,此时事务 B 需要对该行所在的表添加锁,那么事务 B 如何判断 该表已经被加过锁? 难道事务 B 需要对该表一行一行判断是否有行级锁? 显然不是

实际上是事物 A 对某一行添加排他锁的同时,在行级锁的上层如页锁、表锁均添加了意向锁,事务 B 只需要判断该表有没有意向锁即可

特点
#

自增锁 AUTO-INC Lock
#

3 种锁定模式
#

元数据锁 MDL Lock
#

其实就是针对于表结构/表元数据的锁,分为 MDL 读锁和 MDL 写锁

MDL 读锁就是不影响表结构时添加的,对表数据的增删改查不涉及表结构的修改

MDL 写锁就是需要修改表结构时需要添加的

行锁
#

记录锁 Record Lock
#

间隙锁 Gap Lock
#

间隙锁随着对行记录加 S 锁时自动加上

select * from user where id = 1 for share ;

查看锁(显式锁)

Select * from performance_schema.data_locks\G

查看显示锁,insert 的隐式锁看不到的

间隙锁会出现死锁问题,当两个事务均拥有某个范围的间隙锁、且在进行插入操作时,就会出现死锁

解决死锁策略(下面死锁部分详述)
#
  1. 各自等待直至某一方超时,超时方自我回滚且释放锁
  2. 检测机制检测到死锁后,回滚某个事务,释放其持有的锁,即手动破坏死锁

临键锁 Next-Key Lock
#

其实就是 行记录锁 + 间隙锁

当给一个范围时,这个范围内匹配的数据行会加记录锁,间隙部分会加间隙锁

插入意向锁 Insert Intention Lock
#

是在 insert 操作时产生的。是一种特殊的间隙锁

页锁
#

从对待锁的态度划分
#

秒杀案例 - 不使用锁

悲观锁 Pessimistic Lock
#

适合写操作多的场景,加锁具有排他性

秒杀案例 - 使用悲观锁

使用悲观锁,要确定使用索引,否则会导致因顺序扫描使当前事务不需要数据行也上锁,开销大也影响并发性能

乐观锁 Optimistic Lock
#

适合读操作多的场景,无死锁

乐观锁的版本号机制
#

乐观锁的时间戳机制
#

秒杀案例 - 使用乐观锁

两种锁的对比、适用场景
#

按加锁的方式划分
#

隐式锁
#

对隐式锁的理解:
#

insert 时不加锁(含隐式锁,无锁),可以避免加锁的开销,当其他事务来读写时给当前事务的 insert 加锁(转化成显式锁),是乐观态度的体现,当出现情况时再悲观处理。而在 insert 过程中,其他事务来读写是可能发生的,若不发生就减少了加锁的开销,若发生则加锁,这就是延迟加锁!

对 insert 的情况,理解 隐式锁 与 插入意向锁
#

插入意向锁,是当前事务想 insert 却发现存在临键锁时,会加的一个锁

而隐式锁,是当前事务 A 进行 insert (进来时未被干扰即没有临键锁,产生了隐藏列)且还未提交时,含有隐式锁/无锁(查锁查不出来的,内存中不存在的),另一个事务 B 进来了发现该隐藏列中的 trx_id(事务 A)依然活跃,为事务 A 创建了锁。同时事务 B 也为自己创建一个锁进行等待

显式锁
#

通过 Select * from performance_schema.data_locks 能查看得到的

通过特定语句加的锁

Select .. For share;

Select … For update;

全局锁
#

死锁
#

双方都持有对方的资源、双方都在等待对方释放,双方都不主动释放对方的资源

产生死锁的必要条件
#

如何处理死锁
#

超时等待
#

死锁检测
#

如何避免死锁
#

锁的内存结构
#

符合以下条件的记录会放在一个锁结构中:

锁结构
#

锁监控
#

其他监控方法
#

三张表
#

5.7 之前,

使用 information_schema 库的表 innodb_trx、innodb_locks、innodb_lock_waits 表

其中,innodb_locks 表中只能看到阻塞事务的锁,不能看到未阻塞事务的锁

8.0 后,

沿用 information_schema.innodb_trx,

并使用 performance_schema.data_locks(所有的锁) 代替了原先的 information_schema.innodb_locks,除了能看到阻塞事务的锁,未阻塞事务的锁也能看到了;

使用 performance_schema.innodb_lock_waits(等待的锁) 代替了原先的 information_schema.innodb_lock_waits