一、锁的基本分类和定义
按照粒度划分:行锁、表锁、间隙锁
行锁:每次操作锁住一行或多行记录,锁定粒度最小,发生锁冲突概率最低,并发读最高。
表锁:每次锁住整张表。锁定粒度大,发生冲突的概率最高,并发值最低。
间隙锁:每次锁定相邻的一组记录,锁定粒度结余行锁和表锁之间。
按操作类型可分为:读锁和写锁
读锁(S锁):共享锁,针对同一份数据,多个事务可以对其添加读锁,其他事务无法进行修改数据(其他事务无法添加写锁)。
写法:SELECT … LOCK IN SHARE MODE
写锁(X锁):排他锁,针对同一份数据,在当前上锁事务完成前,会阻塞其他事务的写锁或读锁。(仍旧是可以进行查询操作的,只不过不能加锁)。
写法:SELECT … FOR UPDATE
意向锁:
不是真正意义上的锁,只是用于辅助判断,提高加锁判断效率。在对表记录添加S或X锁之前,会先对表添加IS或IX锁,方便下一次加锁前条件判断,不必遍历事务判断。
按照操作的性能可分为:悲观锁和乐观锁
乐观锁:比较乐观,在数据修改的时候才去比较数据版本,一般通过业务层实现判断,提升并发量。
悲观锁:比较悲观,在数据修改之前,就对数据加了写锁,防止数据被其他事务修改。
乐观锁使用场景:
读和写之间时间跨度比较大的业务场景,一般是编辑资料场景,建议使用乐观锁。
比如:电商系统后台,有多个管理员同时间段内对某个商品进行编辑操作。
AB两个员工,读取出来都是V1版本的商品数据,但是B提交前,A先提交了版本V2,用乐观锁,那么B在提交的时候,由于当前版本不是B最开始读取数据的版本,那么系统可以提示用户核对差异数据,确认后在进行提交操作。(当然这种场景还有其他解决方案,比如利用socket实时显示用户之间的修改,这种体验更好,但是开发成本更大,具体根据业务实际需求进行取舍)。
悲观锁使用场景:
读和写时间跨度比较小的业务场景 或 写操作比较频繁(冲突率比较高)的场景。
比如商品下单业务,业务中需要先判断商品库存是否充足,然后在进行库存扣减操作。如果没有对操作加锁,那么容易发生负库存操作。
二、行锁解析
行锁的原理和算法
行锁的原理:对索引数据页上的数据记录加锁实现的。主要实现算法有三种:RecordLock、GapLock、Next-key Lock。
RecordLock:记录锁,锁定单行记录。(RC和RR隔离级别支持)
GapLock:间隙锁,锁定索引记录间隙,确保索引记录间隙保持不变。(RR隔离级别支持)
Next-Key Lock:记录锁和间隙锁组合,同时锁住数据和锁住间前后范围。(RR隔离级别支持)
Next-Key Lock
在RR隔离级别下,innodb对于加锁行为,优先采用Next-Key Lock,当SQL操作含有唯一索引时,innodb会对所进行优化,降级为RecordLock,仅锁住索引的本身而非范围。
Next-Key Lock的解释:
锁住当前记录值+锁住相邻记录值之间的间隙。
RR级别下,默认加锁演示:
表结构:
注意索引a为非唯一索引。
CREATE TABLE innodb_lock (
id int(11) NOT NULL AUTO_INCREMENT,
a int(10) NULL DEFAULT NULL,
b int(10) NULL DEFAULT NULL,
PRIMARY KEY (id) USING BTREE,
INDEX a(a) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
数据:
INSERT INTO innodb_lock VALUES (1, 2, 5);
INSERT INTO innodb_lock VALUES (2, 6, 5);
INSERT INTO innodb_lock VALUES (3, 10, 5);
事务1:
begin
select * from innodb_lock where a=6 lock in SHARE MODE
事务2:
begin
insert INTO innodb_lock (a)VALUES(5)
事务1对a=6加读锁或者排它锁后,其他事务,无法插入或修改a值为3-9的数据。(6为记录本身,2-5和7-9为间隙)