当前位置: 首页 > 科技观察

美团面试有特色:写一个SQL语句然后问加什么锁

时间:2023-03-20 19:56:18 科技观察

美团问了很多数据库,特别喜欢考手写SQL然后问你这个SQL语句加什么锁,你会找其他公司面试很少是这样进行的,所以很多朋友遇到这种问题都会一头雾水。本文将详细总结InnoDB存储引擎中行锁的加锁规则,并举例说明。首先,众所周知,InnoDB有三种行锁:RecordLock(记录锁):锁定某一行记录GapLock(间隙锁):锁定一个左右开的section,哪些语句会被锁住在左开和右关区间?1)对于普通的DML语句(如UPDATE、DELETE和INSERT),InnoDB会自动给对应的记录行加写锁2)默认情况下,InnoDB不会为普通的SELECT语句加任何锁,而是在Serializable隔离级别的Row级读锁将在下面添加。以上两种都是隐式锁。InnoDB也支持通过特定语句显式加锁:3)SELECT*FROMtable_nameWHERE...FORUPDATE,加上行级写锁4)SELECT*FROMtable_nameWHERE...LOCKINSHAREMODE,加行的预知级读锁就不过多介绍了。在学习具体的行锁加锁规则之前,小伙伴们需要先记住两条核心的加锁规则:1)只有搜索过程中访问到的对象才会被加锁这句话怎么理解?例如,有10条主键id为12345...10的记录,我们需要找到id=7的记录。注意查找并不是从第一行开始逐行遍历,而是根据B+树的特性进行二分查找,所以一般的存储引擎只会访问记录行(id=7)的相邻区间来发现2)加锁的基本单位是Next-keyLock。以下示例将帮助您分析InnoDB在SQL语句上自动添加了多少锁。假设有这么一张user表,id为主键(唯一索引),是普通索引(非唯一索引),b是普通列,上面没有索引:id(唯一索引))a(非唯一索引)b104Alice158Bob2016Cilly2532Druid3064Erik案例一:唯一索引等价查询当我们使用唯一索引进行相等性查询时,根据查询记录是否存在,加锁规则会有所不同:当查询记录存在时,Next-keyLock会退化为记录锁;当查询记录不存在时,Next-keyLockLock会退化为间隙锁查询记录存在首先我们看一个查询记录存在的情况:select*fromuserwhereid=25forupdate;结合加锁的两个核心:只有查找过程中访问到的对象才会加锁+加锁的基本单位是Next-keyLock(左开右闭),我们可以分析出这条语句的加锁范围是(20,25]但是,因为这条唯一索引等价查询的记录id=25是存在的,所以,Next-keyLock会退化为记录锁,所以最终的锁定范围是id=25。查询到的记录不存在。再看查询记录不存在的情况:select*fromuserwhereid=22forupdate;结合加锁的两个核心:只有查找过程中访问的对象才会加锁+加锁的基本单位是Next-keyLock(左开右闭),我们可以分析一下,这条语句的加锁范围是(20,25]为什么是(20,25]而不是(20,22],因为id=22的记录不存在,InnoDB首先查找id=20的记录,发现不匹配,继续Lookingdown,发现id=25,所以扫描了id=25的行,所以整体锁定范围为(20,25]由于这条唯一索引等效查询的记录id=22不存在,所以,Next-keyLock会退化为gap锁,所以最终锁定在主键id上的作用域是GapLock(20,25)case2:uniqueindexrangequeryuniqueindexrangequery的规则和equivalentquery的规则是一样的,只有一点不同,就是uniqueindex的rangequery需要一路遍历到对第一个不满足条件的Record的权利,下面分析案例:select*fromuserwhereid>=20andid<22forupdate;先看语句查询条件id>=20的前半部分,因此,这条语句一开始要找的第一行是id=20,结合加锁的两个核心,需要加上Next-keyLock(15,20]并且因为id是唯一索引,存在id=20的记录,所以会退化为记录Lock,即只锁定id=20的行。再来看语句查询条件id<22的后半部分,因为是范围搜索,所以会继续找第一条不满足条件的记录,也会找到id=25这一行和stop,然后加Next-keyLock(20,25],重点来了,但是因为id=25不满足id<22,会退化成间隙锁,加锁范围变成(20,25).因此,上述语句对主键id的最终锁定范围为RecordLockid=20和GapLock(20,25)情况3:非唯一索引等价查询当我们使用非唯一索引等价查询时,根据查询到的记录是否存在,加锁规则会有所不同:当查询到的记录存在时,除了Next-keyLock之外,还会额外加一个间隙锁(规则是向下遍历到第一个那个值满足条件可以停止),即为了好记会加两把锁,即搜索记录左边区间加Next-keyLock,右边区间加Gap锁。当查询记录不存在时,Next-keyLock会退化为间隙锁(此规则与唯一索引的等效查询相同)。查询记录存在。先来看一个查询记录存在的情况:select*fromuserwherea=16forupdate;结合locking的两个核心,这个语句会先对普通索引a加上Next-keyLock,范围是(8,16]并且因为是非唯一索引等价查询,查询记录a=16存在,所以也会加Gap锁,规则是向下遍历到第一个不合格的值停止,所以gap锁的范围是(16,32)。所以上面语句的最终加锁范围在普通索引a为Next-keyLock(8,16]和GapLock(16,32)查询记录不存在我们来看查询记录不存在的情况:select*fromuserwherea=18forupdate;结合加锁的两个核心,这条语句首先会在普通索引a上加一个Next-keyLock,范围是(16,32]但是由于查询记录a=18不存在,所以Next-key锁会退化为间隙锁,即finally在普通索引a上面的锁定范围是(16,32)案例4:非唯一索引范围查询ra的区别nge查询和等价查询在上面唯一索引章节已经介绍过,即范围查询需要一直向右遍历到第一个不满足条件的记录,不像唯一索引范围查询,非-唯一索引范围查询不会退化为RecordLock或GapLock。select*fromuserwherea>=16anda<18forupdate;先看语句查询条件a>=16的前半部分,因此,这条语句一开始要找的第一行是a=16,结合locking为两个核心,Next-keyLock(8,16]需要加上,虽然存在非唯一索引a=16,但是此时不会像唯一索引一样退化成记录锁,接下来看语句查询条件a<18的后半部分,因为是范围查找,所以会继续查找第一条不满足条件的记录,即查找id=32的行并停止,然后添加Next-keyLock(16,32].虽然id=32不满足id<18,但是此时不会像unique索引那样退化为间隙锁。因此,上述语句对普通索引a的最终锁定范围是Next-键锁定(8,16]和(16,32],即(8,32]。