这次咱们来分析一波事务隔离级别的其中一种实现方式,锁
锁究竟锁住的是什么;
共享锁、排它锁、自增锁
临键锁、间隙锁、记录锁
锁的类型
从粒度上划分,可以分为行锁和表锁(基于innodb引擎)
从类型上划分,可以分为共享锁、排他锁、意向共享锁、意向排它锁、自增锁
共享锁 又称读锁,简称S锁
共享锁允许多个事务对于同一数据可以共享同一把锁,能访问到数据,但是只能读不能修改
加锁释放锁方式
select * form student where id=1 LOCK IN SHARE MODE
commit/rollback
排它锁 又称写锁,简称X锁
排它锁不能与其他任何锁并存,如一个事务获取一个数据行的排他锁,其他事务就不能在获取该行的锁(共享锁、排它锁),只有该获取了排它锁的事务是可以对数据进行读取和修改
加锁释放锁方式
自动:delete / update / insert / 默认加锁
手动 select * from student where id = 1 FOR UPDATE
commit/rollback
意向锁 为innodb引擎维护的,用户不能主动操作,为表级别的锁,在加共享锁或排他锁之前,系统会自动加上其对应的意向锁,换言之,如果一个表中如果有意向锁,则其表中必定有某些行持有期对应的行锁
意向锁,可以也理解成一种标记,当一个需要锁表的操作过来的时候,发现此表存在意向锁,那么我也就不需要一行一行的扫秒、加锁了。
自增锁 是针对自增列自增长的一个特殊的级别锁
新增数据时,若事务未提交,ID将永久丢失
模拟一种情景,不好截图,请脑补:新建一张表,主键id自增和name;开启事务 插入一条数据 并提交;后又连续插了几天数据,现在id为5;此时再开启事务 插入一条数据,然后回滚;当我在插入数据时,这是新数据的id为7。
这就是自增锁。
锁的内容
行锁,锁的是什么东西,先看一个表结构,不特别解释,下面都以此表为例。
先抛结论吧:行锁,其实锁的并不是行,锁的是索引
1、如果一个事务内锁的是name,此时name上没有索引,他就不知道该怎么办了,就只能锁定了全表。比如:
事务1:
BEGIN;
SELECT * FROM t2 WHERE name = '1' for update;
COMMIT;
ROLLBACK;
事务2:
BEGIN;
SELECT * FROM t2 WHERE name = '4' for update;
COMMIT;
ROLLBACK;
第二个事务是读不到数据的,因为锁定了全表,而不只是一行,如果此时,两个事务都是以id为条件,那就可以查到数据了。
2、假定此时给name建立索引,如果一个事务内锁的是name,第二个事务内也不能通过其对应的id找到其对应的数据,但是和其他行数据不冲突。比如:
事务1:
BEGIN;
SELECT * FROM t2 WHERE name = '1' for update;
COMMIT;
ROLLBACK;
事务2:
BEGIN;
SELECT * FROM t2 WHERE id = 1 for update;
COMMIT;
ROLLBACK;
提示:加锁时一定要加载索引上,避免锁定全表,同时索引失效时也是同样的道理
行锁的算法
因为临键锁的存在,所以innodb中,Repeatable Read下就已经可以解决幻读的问题了。
Next-Key 临键锁
临键锁是innodb上默认的行锁算法。在一些情况下会退化成间隙锁或记录锁
同样以上面的表结构为例,有4条记录,划定了5个区间,如果把记录划分到左边的区间内,改为左开右闭。
我们这就可以暂定理解为这几个概念,记录,间隙,临键,看上面的图的话,应该是可以理解的。
BEGIN;
SELECT * FROM t2 WHERE id > 4 AND id < 9 for update
COMMIT
ROLLBACK
看上面的sql语句,根据临键锁的算法,此时会锁定查询出的数据7所在的区间,以及7所在区间的下一个区间,所以此时的锁定的区间为:(4,7]、(7,10]。
此时,在这几个区间内都会被锁定的,即使此时插入一个为id为5的数据。
如果查询的数据为空时,退化成间隙锁
Gap 间隙锁
BEGIN;
SELECT * FROM t2 WHERE id > 4 AND id < 7 for update
SELECT * FROM t2 WHERE id = 5 for update
COMMIT
ROLLBACK
上面两个查询的均是锁定(4,7) 区间的情况,这时的两个语句都是没有查到任何内容。
间隙锁有一个特点为,此时一个事务锁定(4,7),允许另一个间隙锁读取此区间的记录,但仍不可再次区间插入数据
Record 记录锁
唯一性(主键/唯一)索引,条件为精准匹配,退化成Record锁
非唯一索引,精准匹配时,会锁记录的左右两个开区间
BEGIN;
SELECT * FROM t2 WHERE id = 4 for update
COMMIT
ROLLBACK
Hello Wrold