畅学锁
1. 操作系统中的常见锁
1.1 互斥锁 & 自旋锁
- 基础
- 互斥锁和自旋锁是最底层的两种锁。
- 互斥锁加锁失败后,线程会释放 CPU ,给其他线程。
- 自旋锁加锁失败后,线程会忙等待,直到它拿到锁。
- 什么时候使用互斥锁,什么时候使用自旋锁?
- 互斥锁加锁失败后,会从用户态陷入到内核态切换线程,虽然简化了使用锁的难度,但是存在一定的性能开销成本,即两次线程上下文切换的成本。如果能够确定能够在短时间内获取到锁,就不应该使用互斥锁,而是使用自选锁。
- 单核CPU不能使用自选锁,因为自选的线程永远不会放弃CPU。
1.2 读写锁
- 当写锁没有被线程持有时,多个线程能够并发地持有读锁。
- 一旦写锁持有后,获取读锁的操作会被阻塞。
根据实现的不同,读写锁可以分为 读优先锁 和 写优先锁,但是有可能造成读写饥饿。
1.3 悲观锁 & 乐观锁
- 悲观锁:访问共享资源前,先上锁。
- 乐观锁:先修改共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。
乐观锁全程不加锁。只有在冲突概率非常低,且加锁成本非常高的场景时,才考虑使用乐观锁。
2. Mysql中的锁
2.1 全局锁
数据库处于只读状态
1 | flush tables with read lock |
2.2 表级锁
2.2.1 表锁
1 | -- 表级共享锁,读锁 |
表锁和行锁满足读读共享、读写互斥、写写互斥。
2.2.2 元数据锁
- 不需要显示使用;
- 对一张表进行CRUD操作时,加的是MDL读锁;
- 对一张表做结构变更操作的时候,加的是MDL写锁;
- 目的是保证当用户对表执行 CRUD 操作时,防止其他线程对这个表结构做了变更。
2.2.3 意向锁
(1) 在使用 InnoDB 引擎的表里对某些记录加上 共享锁 之前,需要先在表级别加上一个 意向共享锁。
(2) 在使用 InnoDB 引擎的表里对某些纪录加上 独占锁 之前,需要先在表级别加上一个 意向独占锁。
1 | -- 先在表上加上意向共享锁,然后对读取的记录加共享锁 |
(3) 意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会冲突。
(4) 意向锁只会和共享表锁和独占表锁冲突。
(5) 如果没有意向锁,那么加独占表锁时,需要遍历表里的所有记录判断是否有独占锁;有意向锁之后,在对记录加独占锁前,先会加上表级别的意向独占锁,这样,加独占表锁,直接查该表是否有意向独占锁就可以了。
(6) 意向锁的目的是为了快速判断表里是否有记录被加锁
2.2.4 AUTO-INC锁
(1) AUTO-INC锁是特殊的表锁机制,锁不是再一个事务提交后才释放,而是在执行完插入语句后立即释放。
(2) 在插入语句时,会加一个表级别的AUTO-INC锁,一个事务在持有AUTO-INC锁的过程中,其他食物的插入语句都会被阻塞。
(3) MySQL5.1.22开始,提供了一种轻量级的锁来实现自增。Innodb使用innodb_autoinc_lock_mode 的系统变量控制。
2.3 行级锁
InnoDB支持行级锁。普通sql语句是不会对记录加锁的,因为它属于快照读。
如果需要加锁,使用
1 | -- 对读取的记录加共享锁 |
依旧是读读兼容,读写兼容,写写冲突。
2.3.1 Record Lock 记录锁
- Record Lock 称为记录锁,锁住的是一条记录。
- 记录锁是有 S 锁和 X 锁之分的。
2.3.2 Gap Lock 间隙锁
- 只存在于可重复读隔离级别,目的是为了解决可重复读隔离级别下幻读的现象。
- 间隙锁之间是兼容的。
Q: 可重复读隔离级别下,能够完全解决幻读情况吗?
A: 不可以,如果先快照读,再当前读,中间有其他事务插入满足条件的数据,就有可能产生幻读。
2.3.3 Next-Key Lock 临键锁
- 锁定一个范围,并且锁定记录本身。
2.3.4 插入意向锁
- 一个事务在插入一条记录的时候,需要判断插入位置是否已被其他事务加了间隙锁;如果有的话,插入操作就会发生阻塞,在此期间会生成一个插入意向锁,并设置为等待状态。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Writer-X!