mysql InnoDB 锁机制

锁类型

InnoDB使用的是行级锁,它的行锁有以下两种

  • 共享锁(S锁)
  • 排它锁(X锁)

共享锁

共享锁又被称为读锁,对于加上共享锁的记录,只允许其它事务读取记录或者给改记录增加共享锁,不允许其它事务给改记录增加排它锁,由于InnoDB所有的修改删除操作都会自动增加排它锁,所以,当一个事务给记录加上共享锁以后其它事务就无法对记录做修改操作了。需要注意的是,如果一个事务T给一条记录增加了共享锁,如果没有其它事务同时给这条记录增加共享锁,事务T自己是可以对记录加排他锁来进行更新的

示例:

1
2
3
4
5
6
7
# 事务t1
set autocommit = 0; # 关闭事务自动提交
select * from users where id = 123 lock in share mode;

# 事务t2
set autocommit = 0; # 关闭事务自动提交
update users set name = 'Peppa' where id = 123; # 语句会阻塞直到t1事务提交

排它锁

排它锁又被称为写锁,如果事务T给某条记录加上了排它锁,那么其它事务就无法再继续给该记录增加锁,包括共享锁和排它锁,但是事务T本身是可以给记录重复加锁(虽然没有任何意义)。对于不需要加锁的读操作还是可以顺利进行的

InnoDB通过 xxx for update 语句给记录增加排它锁

InnoDB对于所有的增删改操作都会自动加上排它锁,有的时候我们需要在记录更新以前手动给记录加上排它锁

1
2
3
4
5
6
7
8
9
10
# 事务t1
set autocommit = 0; # 关闭事务自动提交
select * from users where id = 123 for update;
update users set name = 'Peppa' where id = 123;

# 事务t2
set autocommit = 0; # 关闭事务自动提交
select * from users where id = 123 lock in share mode; # 语句会阻塞直到t1事务提交
select * from users where id = 123 for update; # 语句会阻塞直到t1事务提交
update users set name = 'George' where id = 123;

行锁实现方式

InnoDB的行锁是把锁加在了索引上,如果查询条件字段上没有索引,那么行锁会升级为表锁,例如

1
2
3
4
5
6
7
# 事务t1
set autocommit = 0; # 关闭事务自动提交
update users set age = 18 where name = 'Peppa';

# 事务t2
set autocommit = 0; # 关闭事务自动提交
update users set age = 20 where name = 'George'; # 语句会阻塞直到事务t1提交

虽然第一个事务只更新了Peppa,由于name字段上没有索引导致t2无法给George加锁

同样由于行锁是加在了索引上,如果两条查询语句使用的是不同的索引字段,即是两条语句操作的是同一条记录,两条语句也不会产生锁冲突

1
2
3
4
5
6
7
# 事务t1
set autocommit = 0; # 关闭事务自动提交
update users set age = 18 where gender = 0;

# 事务t2
set autocommit = 0; # 关闭事务自动提交
update users set age = 18 where age > 0; # 虽然更新记录可能会跟t1有部分重复,由于使用的不同的索引字段,不会产生锁冲突

间隙锁

当一个加锁操作使用了一个范围查询的时候, InnoDB不光给已经存在的记录加上锁,同时会给符合条件但是不存在的记录加上锁,比如说

1
2
3
4
5
6
7
# 事务t1
set autocommit = 0; # 关闭事务自动提交
select * from users where id > 100 for update;

# 事务t2
set autocommit = 0; # 关闭事务自动提交
insert into users values(900, 'Peppa'); # 虽然之前id=900的记录不存在,该插入语句也会被事务1阻塞

间隙锁一方面可以防止防止幻读,以满足相关隔离级别的要求。另外一方面是为了满足其恢复和复制的需要