mysql更新加锁_⼀条简单的更新语句,MySQL是如何加锁
的?
看如下⼀条sql语句:
#tableT(idint,namevarchar(20))deletefromTwhereid=10;
MySQL在执⾏的过程中,是如何加锁呢?
再看下⾯这条语句:
select*fromTwhereid=10;
那这条语句呢?其实这其中包含太多知识点了。要回答这两个问题,⾸先需要了解⼀些知识。
相关知识介绍
多版本并发控制
在MySQL默认存储引擎InnoDB中,实现的是基于多版本的并发控制协议——MVCC(Multi-Version Conc
urrency Control)(注:与MVVC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。
其中MVCC最⼤的好处是:读不加锁,读写不冲突。在读多写少的OLTP应⽤中,读写不冲突是⾮常重要的,极⼤的提⾼了系统的并发性能,在现阶段,⼏乎所有的RDBMS,都⽀持MVCC。其实,MVCC就⼀句话总结:同⼀份数据临时保存多个版本的⼀种⽅式,进⽽实现并发控制。
当前读和快照读
在MVCC并发控制中,读操作可以分为两类:快照读与当前读。
快照读(简单的select操作):读取的是记录中的可见版本(可能是历史版本),不⽤加锁。这你就知道第⼆个问题的答案了吧。
当前读(特殊的select操作、insert、delete和update):读取的是记录中最新版本,并且当前读返回的记录都会加上锁,这样保证了了其他事务不会再并发修改这条记录。
聚集索引
也叫做聚簇索引。在InnoDB中,数据的组织⽅式就是聚簇索引:完整的记录,储存在主键索引中,通过主键索引,就可以获取记录中所有的列。mysql删除重复的数据保留一条
最左前缀原则
也就是最左优先,这条原则针对的是组合索引和前缀索引,理解:
1、在MySQL中,进⾏条件过滤时,是按照向右匹配直到遇到范围查询(>, 3 and d = 4 如果建⽴(a, b, c, d)顺序的索引,d是⽤不到索引的,如果建⽴(a, b, d, c)索引就都会⽤上,其中a,b,d的顺序可以任意调整。
2、= 和 in 可以乱序,⽐如 a = 1 and b = 2 and c = 3 建⽴(a, b, c)索引可以任意顺序,MySQL的查询优化器会优化索引可以识别的形式。
两阶段锁
传统的RDMS加锁的⼀个原则,就是2PL(Two-Phase Locking,⼆阶段锁)。也就是说锁操作分为两个阶段:加锁阶段和解锁阶段,并且保证加锁阶段和解锁阶段不想交。也就是说在⼀个事务中,不管有多少条增删改,都是在加锁阶段加锁,在 commit 后,进⼊解锁阶段,才会全部解锁。
隔离级别
MySQL/InnoDB中,定义了四种隔离级别:
Read Uncommitted:可以读取未提交记录。此隔离级别不会使⽤。
Read Committed(RC):针对当前读,RC隔离级别保证了对读取到的记录加锁(记录锁),存在幻读现象。
Repeatable Read(RR):针对当前读,RR隔离级别保证对读取到的记录加锁(记录锁),同时保证对读取的范围加锁,新的满⾜查询条件的记录不能够插⼊(间隙锁),不存在幻读现象。
Serializable:从MVCC并发控制退化为基于锁的并发控制。不区别快照读和当前读,所有的读操作都是当前读,读加读锁(S锁),写加写锁(X锁)。在该隔离级别下,读写冲突,因此并发性能急剧下降,在MySQL/InnoDB中不建议使⽤。
Gap锁和Next-Key锁
在InnoDB中完整⾏锁包含三部分:
记录锁(Record Lock):记录锁锁定索引中的⼀条记录。
间隙锁(Gap Lock):间隙锁要么锁住索引记录中间的值,要么锁住第⼀个索引记录前⾯的值或最后⼀个索引记录后⾯的值。
Next-Key Lock:Next-Key锁时索引记录上的记录锁和在记录之前的间隙锁的组合。
进⾏分析
了解完以上的⼩知识点,我们开始分析第⼀个问题。当看到这个问题的时候,你可能会毫不犹豫的说,加写锁啊。这答案也错也对,因为已知条件太少。那么有那些需要已知的前提条件呢?
前提⼀:id列是不是主键?
前提⼆:当前系统的隔离级别是什么?
前提三:id列如果不是主键,那么id列上有没有索引呢?
前提四:id列上如果有⼆级索引,那么是唯⼀索引吗?
前提五:SQL执⾏计划是什么?索引扫描?还是全表扫描
根据上⾯的前提条件,可以有九种组合,当然还没有列举完全。
id列是主键,RC隔离级别
id列是⼆级唯⼀索引,RC隔离级别
id列是⼆级不唯⼀索引,RC隔离级别
id列上没有索引,RC隔离级别
d列是主键,RR隔离级别
id列是⼆级唯⼀索引,RR隔离级别
id列是⼆级不唯⼀索引,RR隔离级别
id列上没有索引,RR隔离级别
组合⼀:id主键 + RC
这个组合是分析最简单的,到执⾏该语句时,只需要将主键id = 10的记录加上X锁。如下图所⽰:
结论:id是主键是,此SQL语句只需要在id = 10这条记录上加上X锁即可。
组合⼆:id唯⼀索引 + RC
这个组合,id不是主键,⽽是⼀个Unique的⼆级索引键值。在RC隔离级别下,是怎么加锁的呢?看下图:
由于id是Unique索引,因此delete语句会选择⾛id列的索引进⾏where条件过滤,在到id = 10的记录后,⾸先会将Unique索引上的id = 10的记录加上X锁,同时,会根据读取到的name列,回到主键索引(聚簇索引),然后将聚簇索引上的name = 'e' 对应的主键索引项加X 锁。
结论:若id列是Unique列,其上有Unique索引,那么SQL需要加两个X锁,⼀个对应于id Unique索引上的id = 10的记录,另⼀把锁对应于聚簇索引上的(name = 'e', id = 10)的记录。
组合三:id不唯⼀索引+RC
该组合中,id列不在唯⼀,⽽是个普通索引,那么当执⾏sql语句时,MySQL⼜是如何加锁呢?看下图:
由上图可以看出,⾸先,id列索引上,满⾜id = 10查询的记录,均加上X锁。同时,这些记录对应的主键索引上的记录也加上X锁。与组合er的唯⼀区别,组合⼆最多只有⼀个满⾜条件的记录,⽽在组合三中会将所有满⾜条件的记录全部加上锁。
结论:若id列上有⾮唯⼀索引,那么对应的所有满⾜SQL查询条件的记录,都会加上锁。同时,这些记录在主键索引上也会加上锁。
组合四:id⽆索引+RC
相对于前⾯的组合,该组合相对特殊,因为id列上⽆索引,所以在 where id = 10 这个查询条件下,没法通过索引来过滤,因此只能全表扫描做过滤。对于该组合,MySQL⼜会进⾏怎样的加锁呢?看下图:
由于id列上⽆索引,因此只能⾛聚簇索引,进⾏全表扫描。由图可以看出满⾜条件的记录只有两条,但是,聚簇索引上的记录都会加上X 锁。但在实际操作中,MySQL进⾏了改进,在进⾏过滤条件时,发现不满⾜条件后,会调⽤ unlock_row ⽅法,把不满⾜条件的记录放锁(违背了2PL原则)。这样做,保证了最后满⾜条件的记录加上锁,但是每条记录的加锁操作是不能省略的。
结论:若id列上没有索引,MySQL会⾛聚簇索引进⾏全表扫描过滤。由于是在MySQl Server层⾯进⾏的。因此每条记录⽆论是否满⾜条件,都会加上X锁,但是,为了效率考虑,MySQL在这⽅⾯进⾏了改进,在扫描过程中,若记录不满⾜过滤条件,会进⾏解锁操作。同时优化违背了2PL原则。
组合五:id主键+RR
该组合为id是主键,Repeatable Read隔离级别,针对于上述的SQL语句,加锁过程和组合⼀(id主键+RC)⼀致。
组合六:id唯⼀索引+RR
该组合与组合⼆的加锁过程⼀致。
组合七:id不唯⼀索引+RR
在组合⼀到组合四中,隔离级别是Read Committed下,会出现幻读情况,但是在该组合Repeatable Read级别下,不会出现幻读情况,这是怎么回事呢?⽽MySQL⼜是如何给上述语句加锁呢?看下图:
该组合和组合三看起来很相似,但差别很⼤,在改组合中加⼊了⼀个间隙锁(Gap锁)。这个Gap锁就是相对于RC级别下,RR级别下不会出现幻读情况的关键。实质上,Gap锁不是针对于记录本⾝的,⽽是记录之间的Gap。所谓幻读,就是同⼀事务下,连续进⾏多次当前读,且读取⼀个范围内的记录(包括直接查询所有记录结果或者做聚合统计), 发现结果不⼀致(标准档案⼀般指记录增多, 记录的减少应该也算是幻读)。
那么该如何解决这个问题呢?如何保证多次当前读返回⼀致的记录,那么就需要在多个当前读之间,其他事务不会插⼊新的满⾜条件的记录并提交。为了实现该结果,Gap锁就应运⽽⽣。
如图所⽰,有些位置可以插⼊新的满⾜条件的记录,考虑到B+树的有序性,满⾜条件的记录⼀定是具有连续性的。因此会在 [4, b], [10, c], [10, d], [20, e] 之间加上Gap锁。
Insert操作时,如insert(10, aa),⾸先定位到 [4, b], [10, c]间,然后插⼊在插⼊之前,会检查该Gap是否加锁了,如果被锁上了,则Insert不能加⼊记录。因此通过第⼀次当前读,会把满⾜条件的记录加上X锁,还会加上三把Gap锁,将可能插⼊满⾜条件记录的3个Gap锁上,保证后续的Insert不能插⼊新的满⾜ id = 10 的记录,也就解决了幻读问题。
⽽在组合五,组合六中,同样是RR级别,但是不⽤加上Gap锁,在组合五中id是主键,组合六中id是Unique键,都能保证唯⼀性。⼀个等值查询,最多只能返回⼀条满⾜条件的记录,⽽且新的相同取值的记录是⽆法插⼊的。
结论:在RR隔离级别下,id列上有⾮唯⼀索引,对于上述的SQL语句;⾸先,通过id索引定位到第⼀条满⾜条件的记录,给记录加上X锁,并且给Gap加上Gap锁,然后在主键聚簇索引上满⾜相同条件的记录加上X锁,然后返回;之后读取下⼀条记录重复进⾏。直⾄第⼀条出现不满⾜条件的记录,此时,不需要给记录加上X锁,但是需要给Gap加上Gap锁吗,最后返回结果。
组合⼋:id⽆索引+RR
该组合中,id列上⽆索引,只能进⾏全表扫描,那么该如何加锁,看下图:
如图,可以看出这是⼀个很恐怖的事情,全表每条记录要加X锁,每个Gap加上Gap锁,如果表上存在⼤量数据时,⼜是什么情景呢?这种情况下,这个表,除了不加锁的快照读,其他任何加锁的并发SQL,均不能执⾏,不能更新,删除,插⼊,这样,全表锁死。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论