Mysql——7、MySQL锁
1 MySQL锁
1.1 Mysql锁的类型
innoDB支持三种行锁定方式:
i. 行锁(Record Lock):锁直接加在索引记录上面(无索引项时演变成表锁)。
ii. 间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变。间隙锁是针对事务隔离级别为可重复读或以上级别的。
iii. Next-Key Lock :行锁和间隙锁组合起来就是 Next-Key Lock。
1.2 mysql行锁
1.2.1 InnoDB行锁的使用条件
InnoDB支持表级锁,也支持行锁,默认是行锁
InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。
1.2.2 InnoDB行锁的类型
InnoDB的行级锁定同样分为两种类型,共享锁和排他锁,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。
如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
四种锁的兼容方式如下:
意向锁是InnoDB自动加的,不需用户干预。
对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE
用SELECT ... IN SHARE MODE获得共享锁,主要用在需要数据依存关系时,来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。
但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE方式获得排他锁。
1.2.3 InnoDB行锁使用过程中会出现的问题
1、 访问不同的行的记录,但使用相同索引键,会出现锁冲突
2、 Mysql发现全表查找比索引查找效率高,就会使用全表查询,就会使用表锁,而不是行锁
看似命中索引,走行锁,结果却是表锁,最终导致锁等待情况
理解:
由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
即便在条件中使用了索引字段,但是否使用索引来检索数据,是由MySQL通过判断不同执行计划的代价来决定的
如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
1.2.4 InnoDB行锁使用过程中问题场景
1.2.4.1 对没有索引的加锁,导致表锁
示例:
1)准备工作:建tab_no_index表,表中无任何索引,并插入数据
2)Session_1: 我们给id=1的行加上排它锁(for update),由于id没有索引,实际上是表级锁;
3)Session_2:我们给id=2的行加上排它锁(for update),由于id没有索引,所以去申请表级锁,但是却出现了锁等待!原因就是在没有索引的情况下,InnoDB只能使用表锁。
备注:MySQL中的for update 仅适用于InnoDB(因为是只有此引擎才有行级锁),并且必须开启事务,在begin与commit之间才生效。
for update是在数据库中上锁用的,可以为数据库中的行上一个排它锁。当一个事务的操作未完成时候,其他事务可以对这行读取但是不能写入或更新,只能等该事务Rollback, Commit, Lost connection…
1.2.4.2 对有索引的键值加锁,会对所有涉及到的数据行加锁
1)准备工作:对id建索引如下
2)Session_1:此时id是有索引的,我们对id=1 and name=1的一行加排它锁;
3)Session_2:访问不同于Session_1的id=1, name=5行,但是索引键值是一样的,照样等待锁,锁冲突了。
1.2.4.3 多个索引时,不同的事务可以使用不同的索引锁定不同的行
不论什么索引,InnoDB都会使用行锁对数据加锁(对有索引的行数据)。
1)准备工作:对tab_no_index追加name索引:alter table tab_no_index add index name(name);
2)Session_1:开启事务对id=1的行加排它锁,即对name=1与name=5两个数据加锁。
3)Session_2:开启事务对name=2行加锁,因为该数据没有被加锁,索引可以获得锁
4)Session_3:再对name=5的数据进行加锁,由于该数据记录已被Session_1锁定,所以等待获得锁。
1.3 Mysql间隙锁
1.3.1 为什么要使用next-key锁(众安)
innoDB的间隙锁只存在于 RR 隔离级别。间隙锁在innoDB中的唯一作用就是在一定的“间隙”内防止其他事务的插入操作,以此防止幻读的发生:
a) 防止间隙内有新数据被插入。
b) 防止已存在的数据,更新成间隙内的数据。
1.3.2 Mysql间隙锁的加锁操作
innoDB默认的隔离级别是可重复读(Repeatable Read),并且会以Next-Key Lock的方式对数据行进行加锁。
Next-Key Lock是行锁和间隙锁的组合,当InnoDB扫描索引记录的时候,会首先对索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录。
当查询的索引含有唯一属性(唯一索引,主键索引)时,Innodb存储引擎会对next-key lock进行优化,将其降为record lock,即仅锁住索引本身,而不是范围。
mysql的隔离级别,可以防止脏读,不可重复读,但不能防止虚读或幻读,即能防止读取未提交的数据,能防止读取已经提交的数据,但不能防止读取已经插入的数据,所以,需要引入间隙锁。
在进行当前读的情况下,对读出的数据的附近的一整个范围(“间隙”)进行加锁,保证满足查询条件的记录不能被插入。
四级隔离级别中,只有在可串行化的级别之下,才可以防止幻读的出现。事务A执行过程中,由于事务B并发插入了一条新数据,事务A两次读数据的内容不一样,出现了“虚幻”的新纪录
1.3.3 Mysql如何防止幻读
MySQL的innoDB引擎在RR级别,不能防止幻读产生,而为了防止幻读的发生,有以下两种措施:
\1) 在快照读(snapshot read)的情况下,MySQL通过MVCC(多版本并发控制)来避免幻读。
快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。
主要应用于无需加锁的普通查询(select)操作。
\2) 在当前读(current read)的情况下,MySQL通过next-key lock来避免幻读。
当前读,读取的是记录的最新版本,并且会对当前记录加锁,防止其他事务并发修改这条记录。
加行共享锁(SELECT ... LOCK IN SHARE MODE )、加行排他锁(SELECT ... FOR UPDATE / INSERT / UPDATE / DELETE)的操作都会用到当前度。
1.3.4 什么情况下使用间隙锁
1、 范围条件查询中范围内的数据
当用范围条件,而不是相等条件检索数据,并请求共享或者排它锁的时候,InnoDB会给符合条件的已有数据记录的索引项加锁;
2、 不在范围内,且并不存在的
对于不在范围内的但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个间隙加锁,这就是所谓的间隙锁。
理解:
1、 如:select * from where id>100 for update
对id大于100的数据对加锁,但是此时数据中id只有1,2….100,101,不仅对存在的101的记录加锁,还会对大于101不存在的数据的间隙加锁。
2、 此外,对使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁,如下:
Session_1:对不存在的id=6的记录加锁
Session_2:插入id=6的记录,也会出现锁等待
1.3.5 何时使用行锁,何时产生间隙锁
1、只使用唯一索引查询,并且只锁定一条记录时,innoDB会使用行锁。
2、只使用唯一索引查询,但是检索条件是范围检索,或者是唯一检索,然而检索结果不存在(试图锁住不存在的数据)时,会产生 Next-Key Lock。
3、使用普通索引检索时,不管是何种查询,只要加锁,都会产生间隙锁。
4、同时使用唯一索引和普通索引时,由于数据行是优先根据普通索引排序,再根据唯一索引排序,所以也会产生间隙锁。
1.4 innodb是怎么实现可重复读的(众安)
通过一致性视图和多版本并发控制(MVCC)实现。
理解:
Repeatable Read(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录(读已经提交的,其实是读早于本事务开始且已经提交的),但是不能看到其他事务对已有记录的更新(即晚于本事务开始的),并且,该事务不要求与其他事务是“可串行化”的
RR隔离级别是怎么保证这一点的呢?
通过一致性视图和多版本并发控制(MVCC)实现。
使用MVCC(多版本并发控制)。InnoDB为每行记录添加了一个版本号(系统版本号),每当修改数据时,版本号加一。
在读取事务开始时,系统会给事务一个当前版本号,事务会读取版本号<=当前版本号的数据,这时就算另一个事务插入一个数据,并立马提交,新插入这条数据的版本号会比读取事务的版本号高,因此读取事务读的数据还是不会变。
如果数据库并发控制引擎是单纯的封锁协议机制,则应该在读取数据的时候,判断数据项是不是其他事务更新过的。可是InnoDB没有这么做,而是通过如下方式,在RR隔离级别下为事务设置了一个“一致性读视图(即快照)”,之后读取数据,就是根据这个快照来获取,这样,就不能看到他晚于本事务的事务对已有记录的更新(更新生成新版本,必然不在旧的快照所限定的范围内)
1.5 mysql的隔离级别
mysql是如何实现可重复读的:通过mvcc多版本并发控制实现的,也就说是会在事物开始的时候,创建快照,之后查询就用这个快照进行,后续事物的更改对这个快照是不可见的,这就实现了可重复读,
mysql实现读已提交,每一个语句执行前,都会重新计算一个新的视图
快照在mvcc中是怎么工作的:innodb中每个事物都会有一个自己的事物id,并且是唯一的,递增的,