隔离级别 RC
测试数据
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` varchar(4) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`b` varchar(4) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`c` varchar(4) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_a` (`a`),
KEY `idx_b` (`b`)
) ENGINE=InnoDB;
insert into test (id,a,b,c) values (null,'1','1','1');
insert into test (id,a,b,c) values (null,'2','2','2');
insert into test (id,a,b,c) values (null,'3','3','3');
insert into test (id,a,b,c) values (null,'2-1','2','2-1');
一.未使用索引的当前读
事务A:
begin;
select * from test
where c = '2'
for update;
commit;
事务B:
begin;
select * from test
where c = '3'
for update;
commit;
这时事务B第二行需要等待事务A commit后才能执行。所以这里是锁了全表吗?不,再看下事务C:
begin;
select * from test
where id = 3
for update;
commit;
这里事务C并不需要等待事务A提交。所以,这里的锁是应该锁在聚集索引上。也就是聚集索引上id为2的这条记录,事务3走的聚集索引查找(type=ref),所以不需要去获取聚集索引上id为2的这条记录的锁。而事务2走的是全表扫描(type=all),则需要去依次获取聚集索引各行的记录锁。
事务A的加锁过程
1.先依次在聚集索引各行的记录上加记录锁。
2.根据where后面的条件过滤后,将未在范围内的记录锁释放。范围内的记录锁不释放直到事务提交
二.使用的非聚集索引的当前读
事务A:
begin;
select * from test
force index (idx_b)
where b = '2'
and c = '2-1'
for update;
commit;
事务B:
begin;
select * from test
force index (idx_b)
where b = '2'
and c = '2'
for update;
commit;
这里用了强制索引force index,强制指定使用b列上的索引,避免这里数据太少mysql会优化成全表扫描。
事务B第二行也会等待事务A commit后才能继续执行。
两个事务的加锁过程都为:
1.先在非聚集索引索引b中,值为2的记录加记录锁。
2.然后会去聚集索引将相应的记录上记录锁
3.最后,会根据where后面其他条件,将未在范围内的聚集索引的记录锁释放,但是非聚集索引上的记录锁不释放,范围内的记录锁不释放,直到事务提交。