6.8 KiB
锁
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时会被释放。