前言
当我们在讨论MySQL数据库InnoDB的锁时,总会想到表锁、行锁、共享锁、意向锁等等。如果没有进行深入了解,我们很难知道它们之间关系是什么,有什么作用。本文主要是对InnoDB的锁之间关系和作用进行讲解。
一、锁的分类
当并发访问数据库时有可能会导致用户修改和读取数据不一致性问题,为了解决这些问题,目前InnoDB存储引擎提供了两种技术解决方案:多版本控制(MVCC)和基于锁的并发控制(LBCC),本文主要讲解基于锁的并发控制。在数据库里有很多锁概念比如乐观锁、悲观锁、表锁、行锁、共享锁、排他锁、记录锁、间隙锁、意向共享锁、意向排他锁、临间锁等,对它们进行分类如下:
使用方式 | 乐观锁 、悲观锁 |
---|---|
范围锁 | 表锁、行锁 |
属性锁 | 共享锁、排他锁 |
状态锁 | 意向共享锁、意向排他锁 |
算法锁 | 记录锁、间隙锁、临间锁 |
二、InnoDB锁的作用
表锁
表锁就是对整张表加锁,表锁又分共享锁和排他锁。当用户对表进行写数据(update、delete、insert)前,首先要获取排他锁阻止其他用户的所有读写操作。只有当排他锁释放了才能获取共享锁,并且多个用户读操作可以共享一把锁,此时其他用户不能对表进行写操作,除非共享锁释放了。
以下是表级读锁用法:
lock table my_table read;
以下是表级写锁用户:
lock table my_table write;
行锁
行锁就是对表的一行或者多行记录进行加锁,行锁又分共享锁和排他锁。共享锁是指多个事务可以对同一数据共享一把锁都能读取到数据,但是不能进行写操作,只有共享锁释放了,才能加排它锁,进行写操作。
以下是共享锁用法:
select …from … lock in share mode
排他锁是指当一个事务对行数据进行写操作前,先获取排他锁,此时其他事务不能对其加排他锁和共享锁,不能进行写操作。只有当排他锁释放了,才能进行加锁。
以下是排他锁用法:
select … from … for update
insert、update、delete操作默认加排他锁
意向共享锁(IS)
事务在给行数据加共享锁之前,首先要获取该表的意向共享锁,这个过程是InnoDB自动加,无需人为干预。
意向共享锁的作用是在给表加写锁前检测是否有行锁,提升加锁效率;若没有意向共享锁,需要全表扫描检测每一行记录是否有加行锁,效率非常低。
意向排他锁(IX)
事务在给行数据加排他锁之前,首先要获取该表的意向排他锁,这个过程是InnoDB自动加,无需人为干预。
意向排他锁的作用是在给表加读写锁检测是否有行锁,提升加锁效率;若没有意向共享锁,需要全表扫描检测每一行记录是否加行锁,效率非常低。
三、行锁算法
在InnoDB存储引擎里行锁算法有三种:
- 行记录锁(record lock),单个行记录上的锁。
- 间隙锁(gap lock),锁定一个范围,不包括记录本身。
- 临间锁(next-key-lock),是行记录锁和间隙锁的结合,锁定一个范围,并且包含记录本身。
行锁算法规则
在InnoDB存储引擎里行锁算法是一种锁机制,比较复杂,需要结合实践才能更好明白它们是如何运行。
首先创建一张test表如下:
create table ‘test’ (
‘id’ int(11) not null,
‘a’ int(11) not null,
‘b’ int (11) not null,
primary key (‘id’)
,
key ‘a’ (‘a’)
);
insert into test values(1,1,1),(3,3,3),(6,6,6),(10,10,10);
场景1:如果查询条件列没有索引,此时会全表扫描,锁住表的所有记录,还会对所有间隙加锁,此时相当于表锁。
事务A | 事务B | 事务C |
---|---|---|
start transaction | ||
select * from test where a = 3 lock in share mode | ||
update test set a=1 where b = 3 | insert into t values(18,18,18) | |
更新阻塞 | 插入受阻 |
场景2:如果查询条件列是等值查询,并且是唯一索引,next-key lock会退化成行锁。
事务A | 事务B | 事务C |
---|---|---|
start transaction | ||
select * from test where id = 3 lock in share mode | ||
update test set a=4 where id = 3 | insert into t values(2,2,2) | |
更新阻塞 | 插入成功 |
场景3:如果查询条件列是等值查询,并且是唯一索引,查询的索引不存在,next-key lock会退化间隙锁。
事务A | 事务B | 事务C |
---|---|---|
start transaction | ||
select * from test where id = 4 lock in share mode | ||
update test set a=4 where id = 6 | insert into t values(4,4,4) | |
更新成功 | 插入阻塞 |
对于事务A,由于id =4不存在,因此会向右遍历,找到一个对象6,next-key lock会退化成间隙锁,范围为(3,6)。
场景4:如果查询条件列是等值查询,并且是非唯一索引,查询等值后加next-key lock,然后继续向后遍历找到一个对象同样会加next-key lock,。
事务A | 事务B | 事务C |
---|---|---|
start transaction | ||
select * from test where a = 4 lock in share mode | ||
update test set a=5 where id = 3 | insert into t values(5,5,5) | |
更新阻塞 | 插入阻塞 |
对于事务A,由于a是非唯一索引,因此使用next-key lock,锁范围为(1,4],继续向右遍历找下一个键值6,使用next-key lock,锁范围为(4,6]。
四、乐观锁和悲观锁
乐观锁和悲观锁只是实现并发控制的方式,为了理清这两者关系和概念,在这里也提一下。
乐观锁
乐观锁是指对于数据冲突保持一种乐观态度,不需要对操作的数据进行加锁,而是提交更新的时候通过一种机制来验证数据是否存在冲突。常见的乐观锁实现方式如版本号机制,给数据库表添加一个版本号“version”字段,读取数据时同时读出版本号,之后修改数据时需要将版本号+1,提交更新后若刚才读取的version和当前数据库的version一样直接更新,否则重试更新操作直到更新成功。
悲观锁
悲观锁是指基于悲观的态度来防止数据冲突,在操作数据时首先要对数据进行加锁。比如InnoDB锁的机制就是通过悲观锁机制来实现。
五、总结
对经常提到的锁进行分类,让我们有了对这些锁的一个全局认识和它们作用是什么,然后就是关于行锁算法有一点我们要知道, 在InnoDB存储引擎默认的隔离级别是repeatable read采用next-key-lock机制解决了幻读问题,read committed隔离级别仅采用record lock算法。