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