2571 字
13 分钟
数据库并发控制:原理、问题与解决方案
在数据库系统中,多个用户或应用程序同时访问和修改数据是常态。并发控制作为数据库管理系统(DBMS)的核心功能之一,负责协调多事务的并行执行,确保事务的隔离性(ACID 特性之一)和数据库的一致性。本文将从并发执行方式、潜在问题、控制机制三个维度,系统梳理并发控制的核心知识点。
一、多事务执行方式
事务的并发执行本质是“如何调度多个事务的操作顺序”,核心目标是在提高系统吞吐量的同时,避免数据不一致。常见的执行方式分为三类:
(1) 事务串行执行
- 定义:同一时刻仅允许一个事务执行,其他事务需等待当前事务提交或回滚后才能启动。
- 特点:
- 优点:天然保证数据一致性,无需复杂控制机制;
- 缺点:完全浪费系统并行处理能力,在多用户场景下响应速度极慢(例如:100个事务需依次执行,总耗时为单个事务耗时之和)。
- 适用场景:单用户系统、事务执行时间极短的场景。
(2) 交叉并发方式(Interleaved Concurrency)
- 定义:多个事务的操作“轮流交叉”执行(例如:事务1执行操作A → 事务2执行操作B → 事务1执行操作C),本质是“逻辑并行、物理串行”。
- 特点:
- 优点:单处理机系统的主流并发方式,能充分利用CPU空闲时间(如事务1等待IO时,事务2可执行),提升系统吞吐量;
- 缺点:需通过调度算法(如两段锁协议)避免数据不一致,否则可能出现丢失修改、脏读等问题。
- 核心逻辑:通过“时间片轮转”或“优先级调度”实现事务操作的交叉执行,模拟并行效果。
(3) 同时并发方式(Simultaneous Concurrency)
- 定义:多处理机系统中,多个事务在不同处理机上同时执行,是“真正的物理并行”。
- 特点:
- 优点:理论上吞吐量最高,能充分发挥硬件多核性能;
- 缺点:依赖多处理机硬件环境,且需解决跨处理机的数据同步问题(如分布式锁),实现复杂度极高。
- 适用场景:大型分布式数据库系统(如MySQL集群、PostgreSQL集群)。
二、事务并发执行带来的问题
当多个事务并行操作同一批数据时,若缺乏有效的控制机制,会破坏事务的隔离性,导致数据库一致性受损。这是DBMS必须解决的核心问题,也是并发控制机制的设计初衷。
具体来说,并发执行可能引发三类典型的数据不一致问题:
1. 丢失修改(Lost Update)
- 场景:两个事务同时读取同一数据并修改,后提交的事务会覆盖先提交事务的修改结果。
- 示例:
- 事务1:读取余额=100 → 扣除50 → 准备提交(余额=50);
- 事务2:读取余额=100 → 扣除30 → 先提交(余额=70);
- 最终结果:事务1提交后余额=50,事务2的修改被丢失(原本应扣除80,余额=20)。
- 本质:两个事务的写操作相互覆盖,未考虑彼此的修改。
2. 不可重复读(Non-Repeatable Read)
- 场景:同一事务内多次读取同一数据,期间其他事务修改并提交了该数据,导致前后读取结果不一致。
- 示例:
- 事务1:第一次读取商品库存=10 → 执行业务逻辑(如生成订单);
- 事务2:修改库存=8 → 提交;
- 事务1:第二次读取库存=8,与第一次结果不一致,导致业务逻辑异常。
- 注意:不可重复读强调“同一事务内的读取一致性”,区别于“幻读”(幻读是读取范围数据时,其他事务插入/删除数据导致结果集行数变化)。
3. 读“脏”数据(Dirty Read)
- 场景:一个事务读取了另一个事务未提交的修改数据,若后续该事务回滚,读取到的数据就是无效的“脏数据”。
- 示例:
- 事务1:修改余额=200(未提交);
- 事务2:读取余额=200(基于事务1的未提交修改);
- 事务1:因业务异常回滚,余额恢复为100;
- 事务2:基于错误的“脏数据”(200)执行后续操作,导致数据不一致。
- 本质:读取了未提交的中间数据,违反了“提交读”原则。
三、并发控制机制的核心任务
并发控制机制的核心目标是“在保证事务隔离性的前提下,最大化系统并发性能”,具体需完成三项任务:
- 对多事务的并发操作进行正确调度(如按封锁协议、时间戳顺序调度);
- 保证事务的隔离性(避免上述三类数据不一致问题);
- 最终维护数据库的一致性(符合业务规则的状态)。
其中,封锁技术是DBMS中应用最广泛的并发控制机制(如MySQL、PostgreSQL均采用),下文将重点详解。
四、封锁技术:并发控制的核心实现
1. 什么是封锁?
封锁是事务在操作数据对象(表、行、索引等)前,向DBMS发出的“锁定请求”。加锁后,事务获得该数据对象的特定访问权限,其他事务需等待锁释放后才能操作同一对象。
- 核心作用:通过“互斥访问”避免并发冲突,是实现事务隔离性的基础。
- 锁的粒度:支持不同层级的锁定(表锁、行锁、页锁),粒度越细(如行锁),并发性能越高,但锁管理开销越大;粒度越粗(如表锁),并发性能越低,但管理简单。
2. 基本封锁类型
DBMS提供两种核心封锁类型,通过组合使用实现不同的隔离级别:
| 封锁类型 | 别称 | 核心权限 | 其他事务权限 | 适用场景 |
|---|---|---|---|---|
| 排它锁(X锁) | 写锁 | 允许事务读取+修改数据 | 不能加任何锁(X/S锁均被禁止) | 数据修改操作(INSERT/UPDATE/DELETE) |
| 共享锁(S锁) | 读锁 | 允许事务读取数据 | 只能加S锁(不能加X锁) | 数据查询操作(SELECT) |
3. 基本锁的相容矩阵
锁的相容性指“一个事务持有某数据的锁后,其他事务能否对该数据加其他类型的锁”,相容矩阵如下(√=允许,×=禁止):
| 当前锁\请求锁 | 共享锁(S) | 排它锁(X) |
|---|---|---|
| 无锁 | √ | √ |
| 共享锁(S) | √ | × |
| 排它锁(X) | × | × |
- 关键结论:
- 多个事务可同时持有S锁(读-读不冲突);
- 任何事务持有X锁时,其他事务无法加锁(写-读、写-写均冲突)。
4. 封锁协议:从“锁”到“隔离性”的规则
封锁协议是“如何加锁、何时释放锁”的规则集合,不同协议对应不同的隔离级别,解决的问题也不同:
| 封锁协议 | 核心规则 | 解决的问题 | 未解决的问题 | 对应隔离级别 |
|---|---|---|---|---|
| 1级封锁协议 | 事务修改数据前加X锁,事务结束(提交/回滚)后释放 | 丢失修改 | 不可重复读、脏读 | 读未提交(Read Uncommitted) |
| 2级封锁协议 | 1级协议 + 事务读取数据前加S锁,读完后立即释放 | 丢失修改、脏读 | 不可重复读 | 读已提交(Read Committed) |
| 3级封锁协议 | 1级协议 + 事务读取数据前加S锁,事务结束后释放 | 丢失修改、脏读、不可重复读 | 无(理论上) | 可重复读(Repeatable Read) |
- 补充说明:
- 3级封锁协议是实现“可重复读”的核心(如MySQL InnoDB的默认隔离级别就是可重复读,基于行锁+MVCC实现);
- 若需解决“幻读”,需在3级协议基础上增加“间隙锁(Gap Lock)”,实现“串行化(Serializable)”隔离级别。
5. 三级协议的核心区别
- 释放S锁的时机:2级协议“读完即释”,3级协议“事务结束才释”——这是解决“不可重复读”的关键;
- 锁的粒度:协议本身不限制锁粒度,实际应用中结合行锁/表锁,平衡并发性能和管理开销(如InnoDB用行锁实现细粒度控制)。
五、补充:其他并发控制机制(简要介绍)
除了封锁技术,DBMS还支持其他并发控制方式,适用于不同场景:
- 时间戳协议:给每个事务分配唯一时间戳,按时间戳顺序调度事务操作,避免锁竞争(适用于读多写少场景);
- 乐观并发控制(OCC):假设事务并发冲突概率低,事务执行时不锁数据,提交前检查是否有冲突,有冲突则回滚重试(适用于高并发读场景,如Redis、MongoDB);
- 多版本并发控制(MVCC):为数据维护多个版本,事务读取历史版本,修改操作生成新版本,避免读-写冲突(InnoDB的核心机制,兼顾并发性能和隔离性)。
六、总结
并发控制的本质是“在并行与一致之间找平衡”:
- 串行执行保证一致,但牺牲性能;
- 并发执行提升性能,但需通过封锁、时间戳等机制避免冲突;
- 封锁协议是最经典的解决方案,3级封锁协议可覆盖大部分业务场景,而MVCC等机制则在高并发场景下提供更优的性能。
对于开发者而言,理解并发控制原理有助于:
- 合理设置事务隔离级别(如敏感业务用可重复读,普通查询用读已提交);
- 避免长事务导致的锁等待/死锁;
- 优化高并发场景下的数据库性能(如减少锁粒度、使用乐观锁)。
数据库并发控制:原理、问题与解决方案
https://firefly.cuteleaf.cn/posts/concurrency-control/ 最后更新于 2025-12-14