当前位置: 首页 > 后端技术 > Java

一篇详解MySQL锁机制的文章

时间:2023-04-01 18:23:29 Java

1.表级锁、行级锁、页级锁有序设计的一个规则。MySQL数据库由于自身架构的特点,拥有多种数据存储引擎。每个存储引擎的锁机制都是针对它们所面对的特定场景进行优化的,所以每个存储引擎的锁机制也有很大的不同。每个MySQL存储引擎都使用三种类型(级别)的锁定机制:表级锁定、行级锁定和页级锁定。1、表级锁表级锁是MySQL存储引擎中粒度最细的锁机制。这种锁机制最大的特点是实现逻辑非常简单,对系统的负面影响极小。所以获取和释放锁的速度都非常快。当然,大锁粒度最大的负面影响就是锁资源争用的概率会最高,导致并发度大大降低。MyISAM、MEMORY、CSV等一些非事务性存储引擎主要使用表级锁。2.行级锁行级锁最大的特点就是锁定对象的粒度很小。因为加锁的粒度小,锁资源争用的概率也最小,可以给应用程序尽可能多的并发处理能力。提高一些需要高并发的应用系统的整体性能。虽然在并发处理能力上有很大的优势,但是行级锁也带来了很多缺点。由于加锁资源的粒度很小,所以每次获取和释放锁需要做的事情比较多,消耗自然就大了。另外,行级锁也是最容易出现死锁的。使用行级锁定的主要是InnoDB存储引擎。3.页级锁定页级锁定是MySQL中特有的锁定级别。页级锁定的特点是锁定粒度介于行级锁定和表级锁定之间,因此获取锁定所需的资源开销及其所能提供的并发处理能力也介于上述两者之间。使用页级锁定的主要引擎是BerkeleyDB存储引擎。4.总结总的来说,MySQL三种锁的特点大致可以归纳为:表级锁:开销小,加锁速度快;没有死锁;加锁粒度大,锁冲突概率最高,高并发率最低;行级锁:开销大,加锁慢;会出现死锁;最小的加锁粒度,最低的锁冲突概率,最高的并发度;页锁:开销和加锁时间介于表锁和行锁时间之间;会有死锁;锁粒度介于表锁和行锁之间,并发度一般。2.共享锁和排它锁InnoDB实现了标准的行级锁,包括两种:共享锁(简称s锁)和排它锁(简称x锁)。对于共享锁,在当前行上加共享锁,不会阻塞其他事务对同一行的读请求,但会阻塞对同一行的写请求。只有释放读锁后,才会进行其他事物的写操作。对于排它锁,会阻塞其他事务对同一行的读写操作,只有释放写锁后,才会执行其他事务的读写操作。简而言之,读锁会阻塞写(X),但不会阻塞读(S)。写锁将阻止读取(S)和写入(X)。对于RR中的InnoDB(MySQL默认隔离级别),对于update、delete和insert语句,会自动给涉及的数据集加一个排他锁(X)。);对于普通的select语句,innodb不会加任何锁。如果我们想在select操作的时候加S锁或者X锁,需要手动加锁。--添加共享锁(S)select*fromtable_namewhere...lockinsharemode--添加排他锁(X)select*fromtable_namewhere...forupdatecopycodeanduseselect...insharemodeto获取共享锁主要用于在需要数据依赖时确认一行记录的存在,保证没有人更新或删除这条记录。但是,如果当前事务也需要更新记录,就可能造成死锁。对于锁定行记录后需要更新的应用,应该使用select...forupdate方法获取排他锁。三、记录锁1、记录锁(RecordLocks)记录锁其实很好理解。锁定表中的记录称为记录锁,简称行锁。例如,SELECT*FROMtestWHEREid=1FORUPDATE;复制代码,会在id=1的记录上加一个记录锁,防止其他事务对id=1的行进行插入、更新、删除操作。需要注意的是,d列必须是唯一索引列或主键列,否则上面语句加的锁会变成键锁(下面会讲键锁)。同时,查询语句必须是精确匹配(=),不能是>、<、like等,否则会退化为临时键锁。复制代码其他实现在通过主键索引和唯一索引对数据行进行UPDATE操作时,也会对行数据加记录锁:--id列为主键列或唯一索引列UPDATESETage=50其中id=1;copycoderecordlock是给record加锁,加锁索引记录,不是真正的数据记录。如果要加锁的列没有索引,全表记录锁recordlock也是排他(X)锁,所以会阻塞其他事务Insert,update,delete。四、间隙锁1、间隙锁(GapLocks)间隙锁是Innodb在RR(repeatableread)隔离级别下为了解决幻读问题而引入的锁机制。间隙锁是InnoDB中的一种行锁。请记住:gaplock锁定的是一个区间,而不是这个区间内的每条数据。例如emp表只有101条记录,empid值分别为1,2,...,100,101,如下SQL:SELECT*FROMempWHEREempid>100FORUPDATEdata,及当请求共享锁或独占锁时,InnoDB不仅会锁定empid值为101的符合条件的记录,还会锁定empid大于101的“gap”(这些记录不存在)。这时候插入empid等于102的数据,如果事务还没有提交,那么就会处于等待状态,无法插入数据。关于间隙锁还有很多话要说。我将单独写一篇文章来分析间隙锁,并在文章中附上一个完整的例子。五、Next-KeyLocks1.Next-KeyLocks(Next-KeyLocks)Next-KeyLocks是记录锁和间隙锁的组合。也可以理解为特殊的间隙锁。幻读问题可以通过临时加锁来解决。每个数据行上的非唯一索引列上都会有一个临时键锁。当一个事务持有数据行的临时键锁时,它会在左开右闭区间锁定一段数据。需要强调的是,InnoDB中的行级锁是基于索引实现的。临时键锁只与非唯一索引列相关,唯一索引列(包括主键列)没有临时键锁。复制代码假设下表:id主键,年龄公共索引这张表中年龄列的潜在键锁是:(-∞,10],(10,24],(24,32],(32,45],(45,+∞],在事务A中执行如下命令:--根据非唯一索引列UPDATE一条记录UPDATEtableSETname=VladimirWHEREage=24;--或者根据non-uniqueindexcolumnSELECT*FROMtableWHEREage=24FORUPDATE;复制代码无论执行上面SQL中的哪一句,如果在事务B中执行如下命令,都会阻塞该命令:INSERTINTOtableVALUES(100,26,'tianqi');显然,事务A在对年龄为24的列进行UPDATE操作的同时,也获取了区间(24,32)的相邻键锁。总结一下,这里就是记录锁、间隙锁、邻键锁总结一下,InnoDB中行锁的实现依赖于索引,一旦锁操作不使用索引,锁就会退化为表锁。记录锁存在于包括主键索引在内的唯一索引中。锁定单个索引记录。间隙锁存在于非唯一索引中,锁定开放范围内的一个区间,基于相邻键锁实现。非唯一索引中存在相邻键锁,在该类型的每条记录的索引上都存在这种锁。它是一种特殊的间隙锁,锁定一个左开右闭的索引区间。六、意向锁1、意向锁意向锁分为意向共享锁(IS)和意向排他锁(IX)意向共享(IS)锁:事务有意向事务中的某些行加共享锁(S锁)table--事务要获取某些行的S锁,必须先获取表的IS锁。从表中选择列。..锁定共享模式;意向排他(IX)锁:事务打算对表中的某些行加排它锁(X锁)——事务要想获得某些行的X锁,必须先获得表的IX锁。SELECT来自表的列...FORUPDATE;首先我们要明白意向共享锁(IS)和意向排它锁(IX)这四个点都是表锁。意向锁是表级锁,与行级锁不冲突,这一点很重要。意向锁由InnoDB自动添加,无需用户干预。意向锁是InnoDB下存在的内部锁。对于MyISAM,没有意向锁这样的东西。这里会有疑惑,因为之前已经有共享锁(S锁)和排它锁(X锁)了。那么为什么需要引入意向锁呢?它能解决什么问题?我们可以理解为意向锁的目的是为了让InnoDB中的行锁和表锁更高效的共存。为什么这么说呢,举个例子吧。比如有一张表,InnoDBRR隔离级别id为主键。事务A获取一行的独占锁但不提交它:SELECT*FROMusersWHEREid=6FORUPDATE;事务B想要获取用户表上的表锁:LOCKTABLESusersREAD;因为共享锁和排它锁是互斥的,事务B在给users表加共享锁的时候必须保证当前没有其他事务持有users表的排他锁。当前没有其他事务对用户表中的任何行持有独占锁。为了检查是否满足第二个条件,事务B必须在保证users表没有排他锁的前提下检查表中每一行是否有排他锁。显然这是一种非常低效的做法,但是有了意向锁,情况就不一样了:事务B只需要检查表上是否有意向共享锁,如果有,说明表中有部分行被锁了共享行锁,因此事务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全部释放。七、Insertintentlock1.Insertintentlock在讲解insertintentlock之前,我们先想一个问题。有一个表id主键,age普通索引。首先,事务A插入一行数据,没有提交:INSERTINTOusersSELECT4,'Bill',15;然后事务B尝试插入一行数据:INSERTINTOusersSELECT5,'Louis',16;请问:1.事务A用的是什么锁?2、事务B会不会被事务A阻塞?插入意向锁是在插入行之前由INSERT操作生成的间隙锁。此锁用于指示插入意图。当多个事务在同一区间(gap)插入多条位置不同的数据时,事务之间不需要相互等待。假设有两条值为4和7的记录,两个不同的事务分别尝试插入两条值为5和6的记录。每个事务都会获取(4,7)之间的间隙锁,但是因为数据行之间没有冲突,所以两个事务之间不会发生冲突(阻塞等待)。综上所述,insertintentlock的特点可以分为两部分:insertintentlock是一种特殊的gaplock——gaplock可以锁定openrange中的一些记录。插入意向锁是不互斥的,所以即使多个事务在同一个区间内插入多条记录,只要记录本身(主键、唯一索引)不冲突,事务之间也不会有冲突等待。需要强调的是,插入意向锁虽然包含意向锁这个词,但它不属于意向锁而是间隙锁,因为意向锁是表锁,插入意向锁是行锁。现在我们可以回答一开始的问题了:1.使用插入意向锁和记录锁。2、事务A不会阻塞事务B,为什么不使用间隙锁呢?如果只是用普通的间隙锁呢?我们看事务A,其实它一共为锁id为4的记录行获取了3个记录锁,年龄区间在(10,15)间隙锁。年龄区间在(15,20)间隙锁。最后,事务A插入了一行数据,并锁定了范围(10,20)。然后事务B尝试插入一行数据:INSERTINTOusersSELECT5,'Louis',16;因为16位于(15,20)区间,而这个区间存在间隙锁,所以事务B不想给自己申请间隙锁,它甚至无法获取该行的记录锁,所以只能乖乖等事务A结束,再执行插入操作。显然,这种方式会导致事务频繁陷入阻塞等待,插入的并发性很差。这时候如果我们回想刚才讲的插入意向锁,不难发现它是如何优雅地解决并发插入问题的。