数据库锁

数据库锁通过限制并发事务对共享资源的访问顺序,保证隔离性并避免写冲突与部分并发异常。

#type / concept #status / growing #tech / dev / backend #resource / database

[!info] related notes

数据库锁

一句话定义

锁是数据库为了控制并发事务对共享资源的访问顺序而施加的限制,用来保护修改、协调读取并维持隔离性。

它要解决什么问题

如果没有锁,多个事务并发访问同一份数据时很容易出问题:

  • 两个事务同时更新同一行,后写覆盖前写
  • 一个事务正在修改,另一个事务在不合适的时机读到中间状态
  • 范围查询和范围更新彼此穿插,导致更难控制的并发异常

锁的本质不是“让数据库变慢”,而是:

  • 规定谁先访问
  • 哪些访问必须等待
  • 冲突发生时谁让路

核心机制 / 工作原理

1. 锁总是附着在某个资源上

数据库里被锁住的对象不一定只是“一行数据”,还可能是:

  • 某一行
  • 某一页
  • 某个索引范围
  • 某张表
  • 某类元数据

所以讨论锁,不能只问“有没有锁”,还要问:

  • 锁住的是哪种资源
  • 锁的粒度有多大
  • 锁会持有多久

2. 锁模式决定别人还能不能继续操作

常见维度包括:

  • 共享锁:通常允许多个读者并发读取,但会限制写
  • 排他锁:持有者要写入,其他事务通常不能再读写该资源
  • 行锁:只锁部分行,粒度细,并发能力更高
  • 表锁:锁整张表,简单但影响范围更大

不同数据库还会有更多细分模式,例如意向锁、范围锁、gap lock、next-key lock 等,它们本质上都是在表达:

  • 我想对这片资源做什么
  • 别人现在还能不能一起做别的事

3. 锁和隔离级别是协作关系

更强的隔离级别,通常意味着数据库更愿意:

  • 提前加锁
  • 延长锁持有时间
  • 把锁范围扩大到“可能影响结果集变化”的区域

所以锁不是独立机制,而是落实 事务隔离级别 的重要工具之一。

4. 锁和 MVCC 是互补,不是二选一

MVCC 能减少很多读写阻塞,但它解决不了所有问题。

数据库仍然需要锁来处理:

  • 写写冲突
  • 某些当前读或显式加锁读
  • 更高隔离级别下的范围保护
  • DDL 和元数据变更

所以“数据库用了 MVCC,所以基本没有锁”是常见误解。

最小例子 / 最小场景

两个事务同时更新同一条库存记录:

  • 如果都不受协调,可能出现覆盖写
  • 数据库通常会让其中一个事务先拿到写锁
  • 另一个事务等待,直到前者提交或回滚

这个等待不是多余开销,而是数据库在保护“最终只能有一个正确写入顺序”。

从理解顺序上,最该先抓住什么

1. 锁的核心不是“有没有”,而是“范围和时长”

影响并发体验的关键通常是:

  • 锁锁住了多少资源
  • 锁持有了多久
  • 是否覆盖了索引范围而不只是单行

这也是为什么短事务和合适索引对锁体验影响很大。

2. 很多性能问题本质上是锁等待问题

例如:

  • 长事务迟迟不提交
  • 大范围更新把太多行或索引区间锁住
  • 热点行被频繁抢写

这些问题表面看像“数据库慢”,底层往往是锁冲突在放大延迟。

3. 死锁是锁系统的正常副作用之一

当事务 A 等 B,事务 B 又等 A,或者更复杂的等待环出现时,就会形成 死锁

数据库通常会:

  • 检测死锁
  • 回滚其中一个事务

所以工程上不能假设“事务只要重试就永远不会冲突”,而应该接受死锁和重试是并发系统的一部分。

边界与易混淆点

  • 有锁不代表性能一定差;没有必要的锁,数据可能更不安全。
  • MVCC 能减少很多读写冲突,但写写冲突仍然绕不开锁。
  • 死锁不是数据库特有概念,但数据库是最常见、最容易遇到死锁的工程场景之一。
  • 锁多不一定意味着数据库设计差,有时是业务竞争确实激烈;真正需要判断的是锁是否过宽、过久、过频繁。
  • “查询语句不会加锁”是错误说法,很多数据库里是否加锁要看读取方式、隔离级别和语句形态。
创建于 2026/5/3 更新于 2026/5/27