Mysql-(四)-mysql中的锁概述
MySQL InnoDB 锁的基本类型
释放锁有两种方式,只要事务结束,锁就会自动事务,包括提交事务和结束事务。
共享锁
Shared Locks (共享锁),我们获取了一行数据的读锁以后,可以用来读取数据, 所以它也叫做读锁。用 select … lock in share mode 的方式手工加上一把读锁。
排它锁
Exclusive Locks(排它锁),它是用来操作数据的,所以又叫做写锁。
只要一个事务获取了一行数据的排它锁,其他的事务就不能再获取这一行数据的共享锁和排它锁。
加锁方式:
- 增删改,都会默认加上一个排它锁。
- 手工加锁,用 FOR UPDATE 给一行数据加上一个排它锁。
意向锁
当我们给一行数据加上共享锁之前,数据库会自动在这张表上面加一个意向共享锁。 当我们给一行数据加上排他锁之前,数据库会自动在这张表上面加一个意向排他锁。
反过来说:
-
如果一张表上面至少有一个意向共享锁,说明有其他的事务给其中的某些数据行加上了共享锁。
-
如果一张表上面至少有一个意向排他锁,说明有其他的事务给其中的某些数据行加上了排他锁。
用途:对当前表的锁状态进行标志;
当我们准备给一张表加上表锁的时候,必须先要去判断有没其他的事务锁定了其中了某些行。如果有的话,肯定不能加上表锁。那么这个时候我们就要去扫 描整张表才能确定能不能成功加上一个表锁,如果数据量特别大,比如有上千万的数据 的时候,加表锁的效率是不是很低?
但是我们引入了意向锁之后就不一样了。我只要判断这张表上面有没有意向锁。
- 如果有,就直接返回失败。
- 如果没有,就可以加锁成功。
行锁的原理
首先我们有三张表,一张没有索引的 t1,一张有主键索引的 t2,一张有唯一索引的 t3。
1. 未基于索引的情况
我们先假设 InnoDB 的锁锁住了是一行数据或者一条记录。
Transaction 1 | Transaction 2 |
---|---|
begin; | |
SELECT * FROM t1 WHERE id =1 FOR UPDATE; | |
select * from t1 where id=3 for update;// blocked ! | |
INSERT INTO t1 (id , name ) VALUES (5, ‘5’); //blocked ! |
现在我们在两个会话里面手工开启两个事务。
在第一个事务里面,我们通过 where id =1 锁住第一行数据。
在第二个事务里面,我们尝试给 id=3 的这一行数据加锁,这个加锁的操作被阻塞了。 我们再来操作一条不存在的数据,插入 id=5。它也被阻塞了。
实际上这里整张表都被锁住了 ,推论-> innoDB未走索引情况下,也如同MYISAM一样,粒度为全表;
2. 基于主键索引的情况
Transaction 1 | Transaction 2 |
---|---|
begin; | |
select * from t2 where id=1 for update; | |
select * from t2 where id=1 for update; // blocked | |
select * from t2 where id=1 for update; // blocked |
使用相同的 id 值去加锁,冲突;
使用不同的 id 加锁,可以加锁成功。
那么,既然不是锁定一行数据,还是锁住了 id 的这个字段呢?
3 基于唯一索引
Transaction 1 | Transaction 2 |
---|---|
begin; | |
select * from t3 where name= ‘4’ for update; | |
select * from t3 where name = ‘4’ for update;// blocked | |
select * from t3 where id = 4 for update; // blocked |
在第一个事务里面,我们通过 name 字段去锁定值是 4 的这行数据。
在第二个事务里面,尝试获取一样的排它锁,肯定是失败的,这个不用怀疑。
在这里我们怀疑 InnoDB 锁住的是字段,所以这次我换一个字段,用 id=4 去给这行数据加锁,又被阻塞了,说明锁住的是字段的这个推测也是错的,否则就不会出现第一 个事务锁住了 name,第二个字段锁住 id 失败的情况。
既然锁住的不是 record,也不是 column,InnoDB 里面锁住的到底是什么呢?
其实 答案就是索引,InnoDB 的行锁,就是通过锁住索引记录来实现的。
引申出来的两个问题:
- 为什么表里面没有索引的时候,锁住一行数据会导致锁表?
- 1)如果我们定义了主键(PRIMARY KEY),那么 InnoDB 会选择主键作为聚集索引。
- 2)如果没有显式定义主键,则 InnoDB 会选择第一个不包含有 NULL 值的唯一索 引作为主键索引。
- 3)如果也没有这样的唯一索引,则 InnoDB 会选择内置 6 字节长的ROWID 作为隐藏的聚集索引,它会随着行记录的写入而主键递增。
所以,为什么锁表,是因为查询没有使用索引,会进行全表扫描,然后把每一个隐藏的聚集索引都锁住了。
- 为什么通过唯一索引给数据行加锁,主键索引也会被锁住?
在 InnoDB 里面,当我们使用辅助索引的时候,它是怎么检索数据的?辅助索引的叶子节点存储的是什么内容?
在辅助索引里面,索引存储的是二级索引和主键的值。比如 name=4,存储的是 name 的索引和主键 id 的值 4。而主键索引里面除了索引之外,还存储了完整的数据。所以我们通过辅助索引锁定一行数据的时候,它跟我们检索数据的步骤是一样的,会通过主键值找到主键索引,然 后也锁定。
Record、GAP、Next-key 概述:
这些数据库里面存在的主键值,我们把它叫做 Record,那么这里我们就有 4 个 Record。
根据主键,这些存在的 Record 隔开的数据不存在的区间,我们把它叫做 Gap,间 隙,它是一个左开右开的区间,如果还有同学分不清开区间和闭区间的区别。
间隙(Gap)连同它左边的记录(Record),我们把它叫做临键的区间,它是一个 左开右闭的区间。
记录锁
当我们对于唯一性的索引(包括唯一索引和主键索引)使用等值查询,精准匹配到 一条记录的时候,这个时候使用的就是记录锁。
比如whereid=1 4 7 10 。
间隙锁
当我们查询的记录不存在,没有命中任何一个 record,无论是用等值查询还是范围 查询的时候,它使用的都是间隙锁,Gap Lock 只在 RR 中存在。
临键锁(rr 级别但解决了幻读的原因)
当我们使用了范围查询,不仅仅命中了 Record 记录,还包含了 Gap 间隙,在这种情况下我们使用的就是临键锁,它是 MySQL 里面默认的行锁算法,相当于记录锁加上间 隙锁。