1. InnoDB存储引擎支持的锁
1.1 S行级共享锁
select * from l where a=8 lock in share mode;
- 默认查询语句是不加任何锁的
- 通过上述查询语句手动加行级共享锁。
- 行级锁都是加在索引上的。所以如果查询条件不走索引则只能加表级锁。
1.2 X行级排它锁
select * from l where a=8 for update;
- 删除和更改语句自动加行级排它锁
- 查询语句可以通过上述方式加排它锁
- 行级锁都是加在索引上的。所以如果查询条件不走索引则只能加表级锁。
1.3 IS
- 意向共享锁,它是表级别的锁。
- 如果想对某张表的行上加S行级共享锁时,一定会先对这张表加IS锁
1.4 IX
- 意向排他锁,它是表级别的锁。
- 如果想对某张表的行上加X行级排他锁时,一定会先对这张表加IX锁。
1.5 AI 自增锁
- 一种特殊的表级锁。如果表中存在一个自增字段,mysql会维护一个自增锁。
- 和自增锁关联的一个参数为innodb_autoinc_lock_mode(mysql默认值取1)
取值 | 说明 |
0:trational表示传统模式 | 任何一个插入语句,都会先申请自增锁。当这条插入语句执行完就释放锁。 |
1:consecutive表示连续模式 | 可以这么理解,如果一个插入语句知道自己要插入多少条记录时,先申请自增锁,在分配好自增值空间就可以释放自增锁了而不用等这条插入语句执行完。但是如果插入语句不确定要插入多少条记录时,还是要在插入语句执行完才会释放锁(如insert ... into ... select ...这种语句,插入的值来源于另一个select语句,不确定插入的记录个数。) |
2:interleaved表示交叉模式 | 插入语句都不会分配自增锁,而是使用较轻量的mutex锁。所以无法保证一条插入语句插入的多条记录的自增值是连续的。 |
1.6 间隙锁
select * from users where id between 10 and 20 from update;
- 会将这个范围锁定。
- 可以阻止其他事务向这个范围内插入新的记录。
1.7 next-key
- 通过行记录锁+间隙锁实现。
- 锁定一个范围,并且锁定记录本身。
- InnoDB对于行的查询,都是采用这种方式,主要目的是解决实时读下的幻读问题。(在mysql的可重复读隔离级别下,通过next-key锁实)
- 总结
- 行锁:加在锁记录上
- 间隙锁:加在记录间隙上(不包括记录)
- next-key:加在锁记录和该记录前面的间隙上
2. 使用说明
show create table b\G
*************************** 1. row ***************************
Table: b
Create Table: CREATE TABLE `b` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL,
`b` int(11) NOT NULL,
`c` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `a` (`a`),
KEY `b` (`b`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
1 row in set (0.00 sec)
select * from b;
+----+----+----+----+
| id | a | b | c |
+----+----+----+----+
| 1 | 1 | 1 | 1 |
| 6 | 6 | 6 | 6 |
| 11 | 11 | 11 | 11 |
| 16 | 16 | 16 | 16 |
| 21 | 21 | 21 | 21 |
| 26 | 26 | 26 | 26 |
+----+----+----+----+
6 rows in set (0.00 sec)
- 以下例子都是基于这张表,共6条记录。
2.1 行锁都是的查询条件需要走索引,不走索引则会加表锁。
session A | session B |
begin; update b set b=b+1 where c=1; | |
begin; update b set b=b+1 where c=6; (阻塞) |
- c列没加索引,所以事务A加的是表锁。
2.2 普通查询语句,没有显示指定加锁,则默认是不加锁,而且还是快照读。
session A | session B |
begin; select * from b; | |
update b set b=2 where id=1; | |
select * from b; (与上一次查询结果一致) |
session A | session B |
begin; | |
update b set b=2 where id=1; | |
select * from b; (能够看到会话B的更新,说明在第一次查询的时候才生成快照,不是事物开始就生成快照) |
2.3 在非唯一索引上的等值查询条件上更新,InnoDB会给其加next-key锁。
session A | session B |
begin; update b set a=a+1 where b=6; | |
insert into b values(7,7,7,7); (阻塞) |
- 因为事务b在非唯一索引的等值查询条件上更新。会加间隙锁。会锁住(1,11)
- 因为表里有b=6这条记录,因为是非唯一索引,会将b=6的左间隙(1,6)和右间隙(6,11)也加锁。所有加锁区间是(1,11)
session A | session B |
begin; select * from b where b=7 for update; | |
update b set a=2 where b=6; (阻塞) |
- 因为表中没有b=7这条记录,7属于区间(6,11)。会给这个区间加间隙锁。
2.4 在唯一索引上的范围条件上加锁,Innodb会将该范围加next-key锁
session A | session B |
begin; select * from b where id>=6 and id<7 for update; | |
update b set b=b+1 where id=11; (阻塞) | |
- 在唯一索引上范围查询[6,7)。但是实际会锁住[6,11]。
- 加锁范围区间的选择是左开右闭。因为next-key是当前key加其前面的间隙
2.5 总结
- Innodb在默认隔离级别下(可重复读),也会尽量去避免幻读。
- 所以它会自动去选择加行锁还是间隙锁还是next-key锁。(这个可以推导出来如何选择加什么锁)
- 这么做的一切原因都是为了避免在实时读(注意是实时读,而不是快照读)下避免出现幻读。所以什么时候加行锁什么时候加间隙锁你根据这个准则去推导即可。
- 快照读通过mvcc就可以避免幻读了。
- select * from b for update;就是实时读。即加锁的读就是是实时读。实时读要读到数据库最新的数据。
- 再次总结
- 默认情况下,innodb在可重复读的事务隔离级别下运行,但是它会通过next-key锁可以实现防止幻读
- 在索引上进行等值查询(唯一索引),给不存在的记录加锁时,优化为加间隙锁
- 在索引上进行等值查询(普通索引),给某个记录加锁时,将该记录左右间隙也加锁。防止其他事务插入
- 索引上进行范围查询,会访问到不满足条件的第一个值为止。