本篇文章对 MySQL 当中的锁机制进行学习,具体的参考资料来自 csview,参考链接如下:https://www.csview.cn/mysql/lock.html。
按锁粒度划分:
按锁的性质划分:
特殊锁
作用
整个数据库将处于只读状态,增删改会被阻塞。
使用场景
全局锁主要用于做全库逻辑备份。
缺陷
当数据库中记录过多时,备份会花费很多时间。备份期间,业务职能读数据,而不能更新数据,这样会造成业务停滞。
改进
在可重复读隔离级别下,备份数据库之前会先开启事务,会先创建 Read View,然后整个事务执行期间都使用这个 Read View。由于 MVCC 的支持,备份期间业务依然可以对数据进行更新操作。即使其他事务更新了表的数据,也不会备份数据库时的 Read View,这样备份期间备份的数据一直是事务开启时的数据。
当事务访问数据库当中的表时,自动为表加 MDL 锁。作用是防止表的结构被修改,保证表结构的一致性。MDL 在事务提交后才会被释放。
意向锁为 InnoDB 引擎所独有。意向锁进一步细分为意向共享锁和意向排他锁。
意向锁的作用是快速判断表里是否有记录被加锁,以:
作用
表中主键通常会被设置为自增,之后在插入数据时,可以不指定主键的值,数据库会自动给这条新增的记录的主键赋值,通过 AUTO-INC 锁来实现。具体来说,在插入数据时,会加一个表级的 AUTO-INC 锁,然后为被 AUTO_INCREMENT 修饰的字段赋递增的主键值。待插入语句完毕时,才会把 AUTO-INC 锁释放掉。在此期间,其他事务若想要向该表插入数据,都将被阻塞,从而保证插入数据的字段的值是连续递增的。
缺陷
大量数据插入的场景下,会影响数据库的性能。
改进
InnoDB 提供了一种轻量级锁实现自增。插入数据时,会为 AUTO_INCREMENT 修饰的字段加上轻量级锁,然后给该字段赋一个自增值,之后就释放轻量级锁,而不需要等待整个插入语句执行完毕才释放锁。
细分为排他锁和共享锁,锁住表中的单条记录。
当表没有索引时,记录锁将锁定隐式主键。
间隙锁仅用于可重复读隔离级别,目的是为了解决该级别下的幻读现象。间隙锁确保数据表当中的某个区间的记录不会被修改。两个事务可以同时持有包含相同间隙范围的间隙锁,不存在互斥关系。
临键锁是记录锁和间隙锁的组合,它会锁定一个范围,并且锁定记录本身。临键锁既能保护该记录,又能防止其它事务将新记录插入到被保护的记录前面的间隙当中。
临键锁是 InnoDB 默认的行锁算法(注意,可重复读隔离级别也是 InnoDB 默认的隔离级别,由于临键锁包含间隙锁,而间隙锁只能用于可重复读隔离级别,意味着临键锁也只能用于可重复读隔离级别)。
一个事务在插入记录时,需要判断插入位置是否被其它事务加了间隙锁。如果有锁,插入操作会阻塞,直到拥有间隙锁的那个事务执行完毕提交为止,在此期间会生成一个插入意向锁,表明当前有事务想要在某个区间插入新记录,但目前处于等待状态。
LOCK TABLES
语句明确要求表锁时;ALTER TABLE
等操作时;死锁指的是两个或多个事务互相持有和请求锁资源,形成循环等待情况。
MySQL 检测和处理死锁的方式:
检测机制
处理方式
意向锁是表级锁,用于提高行锁冲突检测效率。
意向锁的作用是:
意向锁的类型:
悲观锁假设会发生冲突,故先加锁再访问:
SELECT * FROM table WHERE id = 1 FOR UPDATE:
SELECT * FROM table WHERE id = 1 LOCK IN SHARE MODE;
乐观锁假设不会发生冲突,提交时检查版本:
添加版本号字段:
ALTER TABLE products ADD COLUMN version INT DEFAULT 0;
更新时检查:
UPDATE products
SET stock.= stock - 1, version = version + 1
WHERE id = 1 AND version = 1;
检查影响行数确认是否成功。
SKIP LOCKED 跳过已被锁定的行,应用场景是“任务队列处理”。
NOWAIT 遇到锁立即返回错误而非等待,应用场景是“快速失败场景”。
上述两个特性特别适合高并发队列系统和需要快速响应的应用。