编 程 实 战

记录编程学习之路,分享技术实践经验

数据库

数据库事务与锁机制深度解析

2026-07-07 · 阅读 2,980 · 作者 编程实战

事务和锁是关系型数据库最核心的概念之一。理解它们不仅是面试的必修课, 更是写出正确、高效的数据操作代码的基础。本文将深入讲解 ACID、隔离级别、MVCC 和各类锁机制。

ACID 四大特性

  • 原子性(Atomicity):事务中的所有操作要么全部成功,要么全部回滚。通过 Undo Log 实现。
  • 一致性(Consistency):事务前后数据满足所有约束条件。由其他三个特性共同保证。
  • 隔离性(Isolation):并发事务之间相互隔离。通过锁和 MVCC 实现。
  • 持久性(Durability):已提交事务的数据永久保存。通过 Redo Log 实现。

事务隔离级别

隔离级别脏读不可重复读幻读
READ UNCOMMITTED可能可能可能
READ COMMITTED不可能可能可能
REPEATABLE READ (InnoDB 默认)不可能不可能部分解决
SERIALIZABLE不可能不可能不可能
-- 查看当前隔离级别
SELECT @@transaction_isolation;

-- 设置隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 显式事务
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;  -- 或 ROLLBACK

MVCC:多版本并发控制

InnoDB 通过 MVCC 实现非锁定读(快照读),在 REPEATABLE READ 级别下避免不可重复读:

  • 每行记录有 DB_TRX_ID(最近修改的事务 ID)和 DB_ROLL_PTR(回滚指针)
  • 读取时通过 Read View 判断哪些版本可见
  • READ COMMITTED 级别每次查询创建新的 Read View
  • REPEATABLE READ 级别在事务开始时创建 Read View,整个事务期间不变

InnoDB 锁类型

行锁(Record Lock)

-- 对某一行加锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;  -- 排他锁(X 锁)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;  -- 共享锁(S 锁)

间隙锁(Gap Lock)

锁定索引记录之间的间隙,防止幻读。只在 REPEATABLE READ 级别生效:

-- 表中 id 有 10, 20, 30 三条记录
-- 间隙锁锁定区间:(-∞, 10), (10, 20), (20, 30), (30, +∞)
SELECT * FROM users WHERE id BETWEEN 15 AND 25 FOR UPDATE;
-- 锁定 id=20 记录 + (15,20) 和 (20,25) 间隙

临键锁(Next-Key Lock)

行锁 + 间隙锁的组合,InnoDB 默认的行级锁算法:

-- 锁住记录及其前面的间隙
-- 对于 id=20,临键锁锁住 (10, 20] 区间
SELECT * FROM users WHERE id = 20 FOR UPDATE;

死锁

-- 事务 A
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;  -- 锁住 id=1
-- 此时事务 B 执行:
-- UPDATE accounts SET balance = balance - 100 WHERE id = 2;  -- 锁住 id=2

-- 事务 A 继续
UPDATE accounts SET balance = balance + 100 WHERE id = 2;  -- 等待 B 释放锁

-- 事务 B 继续
UPDATE accounts SET balance = balance + 100 WHERE id = 1;  -- 等待 A 释放锁
-- 死锁!InnoDB 检测到后回滚其中一个事务
避免死锁的实践:按照相同的顺序访问资源。 如转账场景,始终先操作 id 较小的账户,再操作 id 较大的账户。

实战:处理高并发扣减库存

-- ❌ 错误的做法
-- 先查询再更新,存在竞态条件
-- SELECT stock FROM products WHERE id = 1;  -- 读到 stock=10
-- UPDATE products SET stock = 9 WHERE id = 1;

-- ✅ 正确的做法:利用行锁原子更新
UPDATE products
SET stock = stock - 1
WHERE id = 1 AND stock >= 1;
-- 检查 affected_rows,为 0 则库存不足

-- ✅ 或使用悲观锁
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
-- stock >= 1 则更新
UPDATE products SET stock = stock - 1 WHERE id = 1;

总结

理解事务和锁是后端开发的必备技能。核心要点:知道每种隔离级别能解决什么问题、 理解 MVCC 的 Read View 机制、区分各种锁类型和应用场景、 以及在实际业务中正确处理并发更新的问题。MVCC + Next-Key Lock 的组合 使 InnoDB 在性能和一致性之间取得了很好的平衡。

← 返回首页