在 MySQL 的可重复读(REPEATABLE READ)隔离级别下,SELECT … FOR UPDATE WHERE id = 3 的加锁范围取决于 id 列所使用的索引类型,以下分情况进行详细分析:
- id 列是主键索引或唯一索引
当 id 列是主键索引或者唯一索引时,执行 SELECT … FOR UPDATE WHERE id = 3 只会对 id = 3 这一行记录加行锁(Record Lock)。
原理
主键索引和唯一索引能够唯一标识表中的每一行记录。数据库在执行查询时,可以通过索引快速精准地定位到 id = 3 这一行。为了保证事务的一致性,防止其他事务对该行数据进行写操作,数据库只会对这一行加锁,而不会对其他行或间隙加锁。这样可以在保证数据一致性的同时,尽可能减少锁的范围,提高并发性能。
示例代码及验证
sql
– 创建测试表,id 为主键
CREATE TABLE test_table (
id INT PRIMARY KEY,
name VARCHAR(20)
);
– 插入测试数据
INSERT INTO test_table (id, name) VALUES (1, ‘Alice’), (3, ‘Bob’), (5, ‘Charlie’);
– 会话 1:开启事务并执行加锁查询
START TRANSACTION;
SELECT * FROM test_table WHERE id = 3 FOR UPDATE;
– 会话 2:尝试更新未锁定的行
START TRANSACTION;
UPDATE test_table SET name = ‘David’ WHERE id = 1;
– 此操作可以正常执行,因为 id = 1 的行未被锁定
– 会话 2:尝试更新锁定的行
START TRANSACTION;
UPDATE test_table SET name = ‘Eve’ WHERE id = 3;
– 此操作会被阻塞,直到会话 1 的事务结束
2. id 列是普通索引
当 id 列是普通索引时,执行 SELECT … FOR UPDATE WHERE id = 3 会加临键锁(Next - Key Lock)。临键锁是行锁和间隙锁的组合,此时加锁范围是 (1, 3]。
原理
在可重复读隔离级别下,为了避免幻读问题,MySQL 采用临键锁机制。当使用普通索引进行等值查询时,数据库不仅会对查询到的 id = 3 这一行记录加行锁,还会对该记录前面的间隙加间隙锁。在有记录 id 为 1、3、5 的情况下,id = 3 前面的间隙是 (1, 3),所以最终的加锁范围是 (1, 3]。这样可以防止其他事务在该间隙插入新记录,从而保证当前事务在后续查询时不会出现幻读。
示例代码及验证
sql
– 创建测试表,id 为普通索引
CREATE TABLE test_table (
id INT,
name VARCHAR(20),
INDEX idx_id (id)
);
– 插入测试数据
INSERT INTO test_table (id, name) VALUES (1, ‘Alice’), (3, ‘Bob’), (5, ‘Charlie’);
– 会话 1:开启事务并执行加锁查询
START TRANSACTION;
SELECT * FROM test_table WHERE id = 3 FOR UPDATE;
– 会话 2:尝试插入间隙内的记录
START TRANSACTION;
INSERT INTO test_table (id, name) VALUES (2, ‘David’);
– 此操作会被阻塞,因为间隙 (1, 3) 被锁定
– 会话 2:尝试更新锁定的行
START TRANSACTION;
UPDATE test_table SET name = ‘Eve’ WHERE id = 3;
– 此操作会被阻塞,直到会话 1 的事务结束
综上所述,在可重复读隔离级别下,SELECT … FOR UPDATE WHERE id = 3 的加锁范围根据 id 列的索引类型不同而不同。主键索引或唯一索引时只锁 id = 3 这一行;普通索引时,锁 (1, 3] 这个范围。