Files
rikako-note/mysql/mysql文档/锁.md
2024-11-13 00:13:27 +08:00

127 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 锁
## lock 和 latch
在mysql数据库中lock和latch都可以被称之为锁但是两者锁包含意义不同。
### latch
latch一般为轻量级的锁要求锁定时间通常非常短否则会影响性能。在innodb中latch可以分为mutex和rwlock。
`latch通常被用于防止代码中临界资源的并发访问`保证并发操作的安全性。latch操作通常没有死锁检测机制。
### lock
lock和latch则稍有区别lock一般针对的是事务用于锁定数据库中的表、页、行等数据。并且`lock一般在数据库事务进行commit或rollback后才释放`
在innodb中lock拥有死锁检测机制。
latch和lock的比较如下
| | lock | latch |
| :-: | :-: | :-: |
| 对象 | 事务 | 线程 |
| 保护 | 数据库内容 | 代码中的临界资源 |
| 持续时间 | 整个事务过程 | 临界资源 |
| 模式 | 行锁、表锁、意向锁 | 读写锁、互斥量 |
| 死锁 | 通过waits-for graph、timeout等机制检测死锁 | 无死锁检测机制 |
在innodb中可以通过`show engine innodb status`以及`information_schema`下的`innodb_trx`来查看锁的情况。
## innodb中的锁
innodb中针对行级锁有如下两种实现
- S lock(共享锁):允许事务针对一行数据进行读取
- X lock(排他锁):允许事务针对一行数据进行更新和删除
S lock和S lock之间可以互相兼容但是S lock和X lock、 X lock和 X lock之间都是不兼容的。当一个事务t持有某行数据的X lock时其他事务必须等待事务t提交或回滚之后才能获取该行数据的S lock或X lock。
除了行锁之外innodb还支持多粒度的锁定允许行级和表级的锁同时存在。为了支持该操作innodb引入了一种额外的加锁方式`意向锁`
### 意向锁
意向锁支持多粒度加锁,允许行锁和表锁共存。例如,`lock tables ... write`会在指定表上加X锁。为了在多个粒度上进行加锁innodb使用了意向锁意向锁是表级锁代表事务稍后需要为表中的数据加上哪种锁。
意向锁分为两种类型:
- IS: 事务旨在为table中独立的行添加共享锁
- IX: 事务旨在为table中的行添加排他锁
例如,`select ... for share`设置IS锁`select ... for update`则设置了IX锁。
意向锁协议如下:
- 在事务获取table中某行记录的s锁之前其必须获取table的IS或更高级别锁
- 在事务获取table中某行记录的x锁之前其必须获取table的IX锁
table level锁的兼容性如下所示
| | x | ix | s | is |
| :-: | :-: | :-: | :-: | :-: |
| x | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
| ix | 不兼容 | 兼容 | 不兼容 | 兼容 |
| s | 不兼容 | 不兼容 | 兼容 | 兼容 |
| is | 不兼容 | 兼容 | 兼容 | 兼容 |
如果要加的锁和已经存在的锁兼容,那么它将被授予请求加锁的事务;要加的锁和已经存在的锁冲突时,则不会授予。请求加锁的事务将会阻塞,一直等待到存在冲突的锁被释放。
如果事务中的锁请求因为与现有锁冲突而无法被授予,并且造成死锁,那么会抛出异常。
> 意向锁和意向锁之间是兼容的,故而意向锁只会和全表扫描相冲突。
引入意向锁的意图是展示有人锁定了表中的某一行,或是将要锁定表中的某一行。
事务和意向锁相关的信息可以通过`show engine innodb status`来展示,其输出如下:
```
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
```
### Record Lock
记录锁是针对一条index record的锁。例如`SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE`语句会阻塞任何其他事务针对`c1 = 10`记录的插入、更新或删除。
records总是会针对index record加锁即使当前table没有定义索引。对于table未定义索引的情况innodb会创建一个隐藏的聚簇索引并使用该聚簇索引来进行record lock。
事务相关record lock的信息可以通过`SHOW ENGINE INNODB STATUS`来展示,其输出格式如下:
```
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 ;;
```
### 一致性读
#### 一致性非锁定读
`一致性非锁定读`是指innodb通过mvcc多版本并发控制来读取行数据。如果当前待读取的行正在执行update或delete操作那么此时`读取行的操作并不会被阻塞,而是会去读取当前被修改行的快照历史版本`。这种行为被成为`非锁定读`。
在非锁定读的场景下事务读取行数据时并不需要等待其他事务持有的X锁释放而是之前读取行数据的历史快照版本。`历史快照版本通过`undo log`来生成`。
非锁定读极大的提高了数据库的并发性一个事务对行数据的写操作并不会阻塞其他的事务对该行进行读取。innodb在默认的隔离级别下默认通过非锁定读来读取数据。
`在不同事务隔离级别下,读取数据的方式可能会不同,并非所有隔离级别都采用非锁定读的读取方式`。
> #### 多版本并发控制mvcc
> 在非锁定读场景下,一行数据通常不会只有一个历史版本,其数据快照并不只有一个,这被称为多版本并发控制。
>
> innodb对read committed和repeatable read隔离级别使用非锁定读但是两种隔离级别`快照定义并不相同`。
>
> #### read committed
>
> 在read committed隔离级别下非锁定读总是会读取行数据`最新的快照`。
>
> #### repeatable read
> 在repeatable read隔离级别下非锁定读则是会读取`事务开始时的行数据版本`。
#### 一致性锁定读
在默认隔离级别下read committed和repeatable read都是用非锁定读但是可以通过语法显式的支持锁定读。`用户可以通过加锁来保证读取数据的一致性`。
在通过select语句对数据加锁时支持加两种类型的锁
- select ... for update
- select ... lock in share mode
`select ... for update`实际是对读取的行记录加上X锁其他事务均不能对该数据添加任何的锁。
`select ... lock in share mode`则是对读取的记录加上S锁其他事务可以向被锁定记录加S锁但是不能加X锁。
`对于非锁定读即使数据被添加了X锁也可以进行读取。`只有通过`for update`或`lock in share mode`在读取时添加X锁或S锁时读取操作才会被阻塞。
并且,`for update`或`lock in share mode`添加的行锁在事务commit或rollback时会被释放。