Mysql锁机制
1对mysql的锁有了解吗?
首先我们要知道,mysql的锁 其实是为了解决在并发事务时所导致的数据不一致问题的一种处理机制,也就是说 在事务的隔离级别实现中,就需要利用锁来解决幻读问题
然后我们可以聊到锁的分类
按锁的粒度可以分为
- 行锁:锁某行数据,锁粒度最⼩,并发度⾼
- 表锁:锁整张表,锁粒度最大,并发度低
- 间隙锁:锁的是⼀个区间
按锁的性质可以分为
- 共享锁:也就是读锁,⼀个事务给某行数据加了读锁,其他事务也可以读,但是不能写
- 排它锁:也就是写锁,⼀个事务给某行数据加了写锁,其他事务不能读,也不能写
还可以分为
- 乐观锁:并不会真正的去锁某行记录,而是通过⼀个版本号来实现的
- 悲观锁:上面所的行锁、 表锁等都是悲观锁
2什么是死锁?怎么解决?
常规回答:死锁它其实是两个或者多个事务在同一个资源上相互占用,同时并请求锁定对方的资源,从而导致恶性循环的现象
解决办法:
- 程序再并发存取多个表的时候,尽量让他们以相同的顺序来访问表
- 在同一个事务中,尽可能做到一次锁定所需要的所有资源,来避免死锁问题
- 对于比较容易产生死锁的部分,可以尝试升级锁的颗粒度,比如使用表级锁,或者分布式事务锁,或者使用乐观锁
3 数据库的乐观锁和悲观锁是什么,如何实现?
首先我们要明白数据库的四大特性,ACID-其中有隔离性,也就是多个事务在并发执行时 他们内部是不能互相干扰的,而悲观锁和乐观锁正是实现隔离性的一种方式
悲观锁:简单的理解就是 它假定会发生并发冲突,在查询完数据的时候就会把事务锁起来,直到提交事务
实现方式:使用数据库中的锁机制–select * for update
乐观锁:它就是说假定不会发生并发冲突,也就是只在提交操作的时候才会检查数据的完整性,在进行修改的时候把事务锁起来
实现方式:版本号机制和cas算法
最后可以总结下:两种锁其实并没有好坏之分,但是却有分别合适的场景,对于乐观锁来说 它其实更适合读多写少的场景,也就是说冲突很少发生的场景,这样的话 就会省去锁的开销,加大系统的吞吐量,相反针对写比较多的场景,乐观锁可能会频繁的冲突,进而导致上层应用不断地retry,这样反倒降低了性能,因此悲观锁更适合写多场景
4 MySQL中InnoDB引擎的行锁是怎么实现的?
首先我们要明白行锁它其实就是记录锁,说白了也就是对表中的记录加锁,简称记录锁,但是注意记录锁-行锁它是锁住索引记录,而不是数据记录,即其实是基于索引来完成的加锁。
你比如说select * from db where a = 1 for update, 这里的for update就是根据条件来完成行锁锁定,并且这里的条件也是有索引建的列,如果a字段不是索引键 将会对整张表加锁
同时记录锁也是排它(X)锁, 所以会阻塞其他事务对其插入、更新、删除。
5 什么是间隙锁-Gap lock?
间隙锁 是 Innodb 在 RR(可重复读) 隔离级别 下为了解决 幻读问题 时引入的锁机制。
而间隙锁它其实也算是行锁的一种,另外尤其注意的是间隙锁 它锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
举例来说,假如student表中只有101条记录,其empid的值分别是1,2,…,100,101,
SELECT * FROM student WHERE sid > 100 FOR UPDATE
当我们用条件检索数据,并请求共享或排他锁时,InnoDB不仅会对符合条件的empid值为101的记录加
锁,也会对sid大于101(这些记录并不存在)的“间隙”加锁。
这个时候如果你插入empid等于102的数据的,如果那边事物还没有提交,那你就会处于等待状态,无法插入数据。
6 什么是临键锁(Next-Key Locks)?
Next-key锁是记录锁和间隙锁的组合,它指的是加在某条记录以及这条记录前面间隙上的锁。
也可以理解为一种特殊的间隙锁。
通过临建锁可以解决 幻读 的问题。
每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。
注意:临键锁只与 非唯一索引列 有关,在 唯一索引列(包括主键列)上不存在临键锁。
举个例子
假设有如下表:id主键, age 普通索引
该表中 age 列潜在的临键锁有:
(-∞, 10],
(10, 24],
(24, 32],
(32, +∞],
在事务 A 中执行如下命令:
– 根据非唯一索引列 UPDATE 某条记录
UPDATE table SET name = Vladimir WHERE age = 24;
– 或根据非唯一索引列 锁住某条记录
SELECT * FROM table WHERE age = 24 FOR UPDATE;
不管执行了上述 SQL 中的哪一句,之后如果在事务 B 中执行以下命令,则该命令会被阻塞:
INSERT INTO table VALUES(100, 26, ‘tianqi’);
很明显,事务 A 在对 age 为 24 的列进行 UPDATE 操作的同时,也获取了 (24, 32] 这个区间内的临键锁。
这里对 记录锁、间隙锁、临键锁 做一个总结:
InnoDB 中的行锁的实现依赖于索引,一旦某个加锁操作没有使用到索引,那么该锁就会退化为表锁 。
记录锁存在于包括主键索引在内的唯一索引中,锁定单条索引记录。
间隙锁存在于非唯一索引中,锁定开区间范围内的一段间隔。
临键锁存在于非唯一索引中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙锁,锁定一段左开右闭的索引区间。
7 什么是意向锁?
意向锁又分为 意向共享锁(IS) 和 意向排他锁(IX)
意向共享(IS)锁:简单理解就是事务有意向对表中的某些行加共享锁(S锁)
即-- 事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
SELECT column FROM table … LOCK IN SHARE MODE;
意向排他(IX)锁:即事务有意向对表中的某些行加排他锁(X锁)
– 事务要获取某些行的 X 锁,必须先获得表的 IX 锁。
SELECT column FROM table … FOR UPDATE;
首先我们要明白四点
意向共享锁(IS)和 意向排他锁(IX)都是表锁。
意向锁是一种 不与行级锁冲突的表级锁,这一点非常重要。
意向锁是 InnoDB 自动加的, 不需用户干预。
意向锁是在 InnoDB 下存在的内部锁,对于MyISAM 而言 没有意向锁之说。(即只针对InnoDB)
那么问题来了,既然前面已经有了共享锁(S锁)、排它锁(X锁)。
又为什么需要引入意向锁呢?它能解决什么问题呢?
我们可以理解 意向锁 存在的目的就是 为了让 InnoDB 中的行锁和表锁更高效的共存 。
也就是说 如果某个事物要加表锁,如果没有意向锁 那么它就需要去检测表中的每一行是否存在排他锁。很明显这是一个效率很差的做法,
但是有了意向锁之后,情况就不一样了:这个事务只要看表上有没有
意向共享锁,有则说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。这样就高效多了
这里我们再来看下 共享(S)锁、排他(X)锁、意向共享锁(IS)、意向排他锁(IX)的兼容性
可以看出 意向锁之间是互相兼容的.那你存在的意义是啥?
意向锁不会为难意向锁。也不会为难行级排他(X)/共享(X)锁,它的存在是为难 表级 排他(X)/共享(X)锁。
注意 这里的排他(X)/共享(S)锁指的都是表锁!意向锁不会与行级的共享/排他锁互斥! 行级别的X和S按照上面的兼容性规则即可。
意向锁与意向锁之间永远是兼容的,因为当你不论加行级的X锁或S锁,都会自动获取表级的IX锁或者IS锁。
也就是你有10个事务,对不同的10行加了行级X锁,那么这个时候就存在10个IX锁。
这10IX存在的目的是啥呢,就是假如这个时候有个事务,想对整个表加排它X锁,那它不需要遍历每一行是否存在S或X锁,而是看有没有存在意向锁,只要存在一个意向锁,那这个事务就加不了表级排它X
锁,要等上面10个IX全部释放才行。
8 什么是插入意向锁?
插入意向锁 的特性可以分成两部分:
插入意向锁是一种特殊的间隙锁 —— 间隙锁可以锁定开区间内的部分记录。
插入意向锁之间互不排斥,所以即使多个事务在同一区间插入多条记录,只要记录本身(主键、唯一索引)不冲突,那么事务之间就不会出现冲突等待。
需要强调的是,虽然插入意向锁中含有意向锁三个字,但是它并不属于意向锁而属于间隙锁,因为意向锁 是表锁而 插入意向锁是行锁 。
总结下:
- InnoDB在RR的事务隔离级别下,使用插入意向锁来控制和解决并发插入。
- 插入意向锁是一种特殊的间隙锁。
- 插入意向锁在锁定区间相同但记录行本身不冲突的情况下互不排斥。
9 MySQL间隙锁,如何解决幻读?
首先我们要知道
在RR的隔离级别下,Innodb使用MVCC和 next-key locks(行锁和间隙锁的组合)解决幻读,
MVCC解决的是普通读(快照读)的幻读,
next-key locks解决的是当前读情况下的幻读。
MySQL间隙锁 + 记录锁 ,组合起来,解决的是当前读情况下的幻读
至于MVCC如何解决幻读的 请看 我之前的事务篇
而在这里 我们简单说下什么是当前读和快照读
快照读(历史数据)-mvcc
快照读对应的sql 语法:简单的select操作(不包括 select … lock in share mode, select … for update)
当前读(最新数据)对应的sql 语法:
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
那么解决原理其实就是 保证数据是一致的(也就是一个事务,其内部读取对应某一个数据的时候,数据都是一样的),同时读取的数据是最新的数据。
innodb提供了next-key lock,也就是结合gap锁与行锁,达到最终目的