记录锁,间隙锁,Next-Key Lock
- mysql的锁机制
- 一、InnoDB行锁的种类
- 1、记录锁(Record Lock)
- (1)不加索引,两个事务修改同一行记录
- (2)不加索引,两个事务修改同一表非同行记录
- (3)加索引,修改同一行记录,不行
- (4)加索引,修改同表的不同行,可以修改
- 2、间隙锁(GAP Lock)
- 3、记录锁和间隙锁的组合(next-key lock)
mysql的锁机制
- 数据库锁机制简单来说,就是数据库为了保证数据的一致性,使各种 共享资源 在被访问时变得 有序而设计 的一种规则。
- MysQL的锁机制比较简单最著的特点是不同的存储引擎支持不同的锁机制。 InoDB支持行锁,(有时也会升级为表锁)MyISAM只支持表锁。
- 表锁 的特点就是开销小、加锁快,不会出现死锁。锁粒度大,发生锁冲突的概率小,并发度相对低。
- 行锁 的特点就是开销大、加锁慢,会出现死锁。锁粒度小,发生锁冲突的概率高,并发度高。
今天我们讲锁主要从InnoDB引擎来讲,因为它既支持行锁、也支持表锁。
看完看笔记可以看下这篇博客,非常优秀:
mysql 索引间隙锁_关于mysql innodb间隙锁的一些思考
或者这个间隙锁
一分钟了解Mysql的间隙锁——《深究Mysql锁》
一、InnoDB行锁的种类
- InnoDB默认的事务隔离级别是RR,并且参数innodb_locks_unsafe_for_binling=0的模式下,行锁有三种。
1、记录锁(Record Lock)
(1)不加索引,两个事务修改同一行记录
事务一:
begin;
update teacher set teacher_no = 'T2010005' where name = 'wangsi';
事务二:
begin;
update teacher set teacher_no = 'T2010006' where name = 'wangsi';
发现卡住了:
事务一提交了,事务二才获取了。
测试我们发现第二个窗口,一直在处理中
并且最后会报一个超时的错误!我们把第一个窗口提交了
然后在执行一次第二个窗口:
我们发现第二个窗口就可以执行了
(2)不加索引,两个事务修改同一表非同行记录
事务一:
begin;
update teacher set teacher_no = 'T2010005' where name = 'wangsi';
事务二:
begin;
update teacher set teacher_no = 'T2010006' where name = 'sunsi';
发现卡住了:
事务一提交了,事务二才获取了。
说明锁的是表!
(3)加索引,修改同一行记录,不行
事务一:
begin;
update teacher set teacher_no = 'T2010005' where name = 'wangsi';
事务二:
begin;
update teacher set teacher_no = 'T2010006' where name = 'wangsi';
发现卡住了:
事务一提交了,事务二才获取了。
(4)加索引,修改同表的不同行,可以修改
事务一:
begin;
update teacher set teacher_no = 'T2010008' where name = 'wangsi';
事务二:
begin;
update teacher set teacher_no = 'T2010009' where name = 'jiangsi';
我们发现也不行,那我们试试在name字段上加索引!
发现都可以顺利修改,说明锁的的确是行。
证明行行锁是加在索引上的,这是标准的行级锁。
所以一定要记住,行锁是加在索引上的,如果没有索引就会加到全表上!
2、间隙锁(GAP Lock)
在RR这个级别下 ,为了避免幻读,引入了间隙锁,他锁定的是记录范围,不包含记录本身,也就是不允
许在范围内插入数据。
查看隔离级别:
show variables like '%iso%';
我们现在把表中id是4的数据改成8,然后开始测试:
begin;
SELECT * from teacher where id = 8 or id = 6 for UPDATE;
我们开启一个事务,查询一下id为8或者6的数据,按照间隙锁的条件,现在上面应该是id为(3-6),下面8就是最大的,也就是说,id从3到无穷大,这个时候都是不能插入的,go!这个地方要注意,
1.普通查询是快照读,是不会加锁的!
2.主键和唯一索引只有在where条件没有全部命中的时候才会产生间隙锁!所以我们要先去掉id字段的主键
然后打开新的窗口进行插入
begin;
insert into teacher values (5,'zhangnan','T888888');
我们关闭所有窗口,插入一条为9的数据:
这个时候我们测试下,理论上锁定的是8到无穷大:
begin;
SELECT * from teacher where id = 10 for UPDATE;
执行sql:
begin;
insert into teacher values (5,'zhangnan','T888888');
插入5可以,我们插入20看看
begin;
insert into teacher values (20,'linda','T232323');
COMMIT;
我们发现又开始卡住了!
当然我们也可以手动加上共享锁:
我们开始写第一个窗口语句:
BEGIN;
SELECT * from teacher where id < 8;
我们写第一个窗口语句:
begin;
insert into teacher values (6,'linda','T232323');
COMMIT;
执行成功,第一个窗口再次执行,产生幻读
我们手动加上共享锁:
BEGIN;
SELECT * from teacher where id < 8 lock in share mode;
第二个窗口开始测试:
begin;
insert into teacher values (5,'linda5','T55555');
COMMIT;
我们发现卡住了,这个地方就加上了一个1-8)的共享锁,这样也能够解决幻读的问题,但是会产生大量的数据锁定:
因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。第二就是尽量不要用范围查询出数据,在去筛选,或者缩小查询范围!
锁的一些总结:【mysql】表锁、行锁、间隙锁、共享锁(读锁)、排他锁(写锁)、Next-Key Locks 之间的关系
如果没有索引,innodb的行锁会升级为表锁,效果和表锁一样,锁住全部索引
3、记录锁和间隙锁的组合(next-key lock)
- 是记录锁和间隙锁的组合,当InnoDb扫描索引时会先对索引记录加上记录锁,在对索记录两边加上间隙锁。
BEGIN;
SELECT * from teacher where id = 6 or id = 8 for update;
我们发现锁定的范围是6-8,5是可以正常插入的
如果使用的是next-key lock,则5是不可以插入的,但是我们的id有唯一索引,所以next-key lock降级为了GAP Lock
InnoDB有三种行锁的算法:
- 1,Record Lock:单个行记录上的锁。
- 2,Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。
- 3,Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。
在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
InnoDB行锁是通过给索引项加锁实现的,如果没有索引,InnoDB会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样