Only the Chinese version of this page is provided currently. The English version will be provided soon.

悲观事务

Last updated: 2026-02-05 11:01:13

悲观锁的基本概念

悲观锁是一种数据库并发控制机制,其核心思想是先取锁,再访问。它假设在多用户并发环境下,数据冲突的概率很高,因此在访问数据之前,先对数据加锁,以防止其他事务修改或读取数据,从而确保数据的一致性。
悲观锁的特点是具有强烈的独占性排他性。当事务需要对数据进行操作时,它会先获取锁,确保在事务执行期间,其他事务无法修改这些数据。这种机制适用于数据竞争激烈的场景,能够有效避免脏读、幻读和不可重复读等并发问题。
与乐观锁相比,悲观锁采取了一种更为保守的策略。乐观锁假设冲突很少发生,在提交操作时才检查冲突;而悲观锁则假设冲突经常发生,提前预防冲突。

悲观锁的实现方式

TDSQL Boundless 中的悲观锁主要通过以下两种 SQL 语句实现:

排他锁(SELECT ... FOR UPDATE

排他锁(Exclusive Lock),也称为写锁,是最常用的悲观锁实现方式。使用SELECT ... FOR UPDATE语句可以对查询到的数据行加排他锁,阻止其他事务对这些数据行进行修改或加锁。
START TRANSACTION;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- 对查询到的数据进行操作
UPDATE orders SET status = 'shipped' WHERE id = 1;
COMMIT;
在此示例中,当事务 A 执行SELECT ... FOR UPDATE后,其他事务如果尝试对同一数据行执行SELECT ... FOR UPDATE或修改操作,将会被阻塞,直到事务 A 提交或回滚。

共享锁(SELECT ... LOCK IN SHARE MODE

共享锁(Shared Lock),也称为读锁,使用SELECT ... LOCK IN SHARE MODE语句可以对查询到的数据行加共享锁。共享锁允许多个事务同时读取同一数据行,但会阻止任何事务对数据行进行修改。
START TRANSACTION;
SELECT * FROM products WHERE id = 1 LOCK IN SHARE MODE;
-- 其他事务可以读取但不能修改这些数据
COMMIT;
共享锁适用于需要确保数据在读取期间不被修改,但允许多个事务同时读取的场景。

悲观锁的锁粒度

TDSQL Boundless 的存储引擎支持行级锁,但实际锁的粒度取决于查询条件是否使用了索引。如果 WHERE 子句中的条件字段有索引,TDSQL Boundless 会使用行级锁;如果没有索引,则可能升级为范围锁。
-- 创建表
CREATE TABLE users (
user_id INT PRIMARY KEY,
username VARCHAR(20)
);

SELECT * FROM users WHERE user_id = 101 FOR UPDATE; -- 行级锁
SELECT * FROM users WHERE username = 'john' FOR UPDATE; -- 范围锁(username 列无索引,锁整张表的数据)
SELECT * FROM users WHERE user_id >= 100 AND user_id < 200 FOR UPDATE; -- 范围锁(只锁 user_id 在 [100, 200) 区间的数据

与 MySQL (InnoDB) 悲观锁行为差异的对比

TDSQL Boundless 与 MySQL (InnoDB) 的悲观锁核心差异源于二者架构设计:
MySQL (InnoDB)​ 的锁是面向物理存储的,锁信息记录的是数据行在磁盘上的“门牌号”(哪个表空间、哪个页、哪一行)。这在其单机顺序存储的背景下非常高效。
TDSQL Boundless​ 的锁是面向逻辑键值的,锁信息记录的是数据行的“身份证号”(主键值)。这种设计与它的分布式、散列存储架构天然契合,使得锁管理可以独立于数据存储节点,实现更好的扩展性。
对比维度
TDSQL Boundless​
MySQL (基于 InnoDB)
核心差异与影响
加锁对象(对谁加锁)​
逻辑键值​ (如 Primary Key = 15)
数据的物理存储位置​ (Space ID + Page Number + 位图)
根本区别:TDSQL Boundless 不关心数据存储在哪个物理位置,直接对唯一的逻辑标识加锁。MySQL (InnoDB) 的锁与数据的物理页面布局强关联。
单条记录加锁
(如:WHERE number=15)​
行为一致:无论记录是否存在,都对逻辑键 primary key = 15 加行级锁。
行为分化:
1. 记录存在:对 number=15 的记录加行级锁。
2. 记录不存在:为防止幻读,会加一个间隙锁,锁定相邻两条记录(如 number=7 和 number=20)构成的区间。
锁的粒度与语义:TDSQL Boundless 的加锁行为更统一、可预测。MySQL (InnoDB) 在记录不存在时会升级为范围更广的间隙锁,可能影响并发插入。
范围查询加锁
(如:WHERE row_id BETWEEN 1 AND 20)​
整体或自适应分解:
1. 无冲突:直接加锁整个逻辑范围 [1, 20]。
2. 有冲突:若范围内某点(如 row_id=15)已被其他事务加锁,则会将加锁范围分解为不连续区间, [1, 15) + [15, 20],并等待锁释放。
分段加锁:根据页面的物理存储顺序,将查询范围拆分成多个小的区间(如 (1,3], (3,5], (5,10]...)进行加锁。
加锁策略:TDSQL Boundless 倾向于从逻辑上维护一个大的锁范围,仅在遭遇锁冲突时分解。MySQL (InnoDB) 的加锁粒度受物理数据页的划分影响,更为细碎。
特点总结​
直接加锁:加锁过程不依赖物理位置,逻辑清晰,特别适合分布式架构。
加锁前需定位:加锁前必须先确定数据所在的物理页面,锁管理与存储结构耦合紧密。
架构适应性:TDSQL Boundless 的实现在分布式环境下扩展性更好。MySQL (InnoDB) 的实现在单机集中式部署中非常成熟高效。