← 全部文章

MySQL 锁相关

MySQL5 min read

目录

MySQL InnoDB 锁的基本类型

共享锁 意向锁 排他锁 乐观锁 悲观锁 锁的基本模式

间隙锁 临建锁 记录锁 锁的算法

https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html

锁的基本模式——共享锁

第一个行级别的锁就是我们在官网看到的 Shared Locks (共享锁),我们获取了一行数据的读锁以后,可以用来读取数据,所以它也叫做读锁。而且多个事务可以共享一把读锁。那怎么给一行数据加上读锁呢?

我们可以用 select lock in share mode;的方式手工加上一把读锁。

释放锁有两种方式,只要事务结束,锁就会自动释放,包括提交事务和结束事务。

确保我们的事务多次读取同一条记录的时候,不被其他的事务修改,还可以避免脏读

锁的释放:事务结束,立刻释放我们的锁。或者事务回滚,也会导致锁的释放,注意:这种情况可能导致事务长时间的阻塞超时,死锁的风险

锁的基本模式——排它锁

第二个行级别的锁叫做 Exclusive Locks(排它锁),它是用来操作数据的,所以又叫做写锁。只要一个事务获取了一行数据的排它锁,其他的事务就不能再获取这一行数据的共享锁和排它锁。

排它锁的加锁方式有两种,第一种是自动加排他锁,可能是同学们没有注意到的:

我们在操作数据的时候,包括增删改,都会默认加上一个排它锁。

还有一种是手工加锁,我们用一个 FOR UPDATE 给一行数据加上一个排它锁,这个无论是在我们的代码里面还是操作数据的工具里面,都比较常用。

释放锁的方式跟前面是一样的。

锁的基本模式——意向锁

意向锁是由数据库自己维护的。

也就是说,当我们给一行数据加上共享锁之前,会自动在这张表上面加一个意向共享锁。

当我们给一行数据加上排他锁之前,会自动在这张表上面加一个意向排他锁。

反过来说:

如果一张表上面至少有一个意向共享锁,说明有其他的事务给其中的某些数据行加上了共享锁。

乐观锁和悲观锁是数据库并发控制的两种核心机制,主要区别在于对数据冲突的假设和处理方式:

悲观锁

悲观锁假设并发冲突频繁,因此在数据访问时立即加锁,阻止其他事务干扰。其特点包括:

  1. 实现方式‌:通过数据库的行锁、表锁或编程语言的 synchronizedReentrantLock实现,例如MySQL的 SELECT...FOR UPDATE语句。
  2. 适用场景‌:
    • 高并发且写操作频繁的环境(如库存扣减);
    • 事务执行时间长或数据一致性要求严格的场景。
  3. 优缺点‌:
    • 优点:保证强一致性,实现简单;
    • 缺点:性能开销大,可能引发死锁或阻塞。

乐观锁

乐观锁假设冲突较少,仅在提交时检查数据是否被修改。其特点包括:

  1. 实现方式‌:
    • 版本号机制:通过 version字段校验,更新时检查版本是否匹配; update products set stock = stock -1 ,version = version +1 where id = 100 and version= 2
    • 时间戳:类似版本号,但使用 last_updated字段对比时间戳。
  2. 适用场景‌:
    • 读多写少的环境(如评论系统);
    • 冲突概率低且需要高吞吐量的场景。
  3. 优缺点‌:
    • 优点:减少锁竞争,提升并发性能;
    • 缺点:需处理冲突重试,可能增加业务复杂度。

对比总结

维度 悲观锁 乐观锁
冲突假设 认为冲突频繁 认为冲突较少
加锁时机 操作前加锁 提交时校验
性能影响 高(阻塞等待) 低(无锁竞争)
典型实现 数据库行锁、Java同步锁 版本号、CAS机制
适用场景 银行转账、库存扣减 社交点赞、统计计数

两种锁的选择需权衡业务场景的冲突概率与性能需求

锁的算法

t2 这张表 id 有一个主键索引。我们插入了 4 行数据,主键 id 分别是 1、4、7、10。

我们这里的划分标准是主键 id。

这些数据库里面存在的主键值,我们把它叫做 Record,记录,那么这里我们就有 4 个 Record。

根据主键,这些存在的 Record 隔开的数据不存在的区间,我们把它叫做 Gap,间隙,它是一个左开右开的区间。

假设我们有 N 个 Record,那么所有的数据会被划分成多少个 Gap 区间?答案是 N+1,就像我们把一条绳子砍 N 刀,它最后肯定是变成 N+1 段。

最后一个,间隙(Gap)连同它左边的记录(Record),我们把它叫做临键的区间,它是一个左开右闭的区间。

如果主键索引不是整型,是字符怎么办呢?字符可以排序吗? 基于 ASCII 码

image.png

记录锁

第一种情况,当我们对于唯一性的索引(包括唯一索引和主键索引)使用等值查询,精准匹配到一

条记录的时候,这个时候使用的就是记录锁。

比如 where id = 1 4 7 10 。

间隙锁

第二种情况,当我们查询的记录不存在,无论是用等值查询还是范围查询的时候,它使用的都是间隙锁。

临键锁

第三种情况,当我们使用了范围查询,不仅仅命中了 Record 记录,还包含了 Gap 间隙,在这种情况下我们使用的就是临键锁,它是 MySQL 里面默认的行锁算法,相当于记录锁加上间隙锁。

比如我们使用>5 <9 , 它包含了不存在的区间,也包含了一个 Record 7。

锁住最后一个 key 的下一个左开右闭的区间。

select * from t2 where id >5 and id <=7 for update; 锁住(4,7]和(7,10]

select * from t2 where id >8 and id <=10 for update; 锁住 (7,10],(10,+∞)

总结:为什么要锁住下一个左开右闭的区间?——就是为了解决幻读的问题。

如果非唯一性的索引,用等值查询,且记录存在,用的什么锁?

临建锁 a = 13 这个记录存在 那么就是 (13)记录锁 (13-14] 间隙锁 临建锁

← 全部文章