一条UPDATE语句在MySQL 8.0中到底加了几把锁?

理解MySQL的锁,可以先从它的架构设计入手。SQL语句进来后,要经过哪些节点:Server层的元数据锁、InnoDB的意向锁(IS/IX)、行级锁,每层管的粒度不同。先看架构设计,再看每层有哪些锁,会更容易理解和记住。

一条update语句,其实会同时涉及元数据共享写锁、InnoDB意向排他锁IX、行级排他锁。

如果能从三层架构入手去理解,后面不管面试还是排查问题,心里都比较有数。

你可以通过下图,先大概看一下,从架构层面,一条update语句是怎么执行的。

锁的分层设计

锁的设计和数据库的分层架构是一致的。

用餐厅来比喻:

  • Server层的元数据锁像前台菜单系统,管能不能改菜单;
  • InnoDB的意向锁(IS/IX)像包厢预定标记,告诉其他服务员这个区域有人要用;
  • 行级锁则是具体哪张餐桌被占着。

SQL语句执行时,先经过Server层检查元数据锁,再进入InnoDB(需要锁行时加意向锁),最后才到行级锁。排查锁问题时,也按这个顺序:先看元数据锁有没有长事务占着表定义,再看InnoDB有没有意向锁,最后看行级锁。

每层架构里的锁

上一节的表格把三层锁介绍了一遍,下面按架构从上到下展开,先说每种锁管什么、典型场景是什么。

Server层:元数据锁MDL

元数据锁保护的是表结构,不是行数据。Server层在语句真正执行前,要先确认这张表的定义还能不能用,于是会在表上加一把元数据锁。

元数据锁按「允许别人做什么」分成几档,面试和工作里最常碰到的是下面三种:

  • 元数据共享读锁(SR):要读这张表的数据,编译执行前得先读到表结构。多个会话可以同时持有,互不影响。
  • 元数据共享写锁(SW):不光要读表结构,还要改行数据,INSERT、update、DELETE、SELECT FOR UPDATE都会申请这把锁。
  • 元数据排他锁(X):要改表结构本身,CREATE、DROP、ALTER必须拿这把锁,同一时刻只能有一个会话持有。

普通的SELECT虽然只读行,但编译和执行阶段仍要读到列名、类型这些表定义,所以会申请元数据共享读锁。多个会话可以同时持有这把锁,互不影响。

ALTER、DROP这类要改表结构的语句则必须拿元数据排他锁,同一时刻只允许一个会话修改表定义。别的会话如果还想再加元数据共享读锁或元数据共享写锁,都要先等着,等前面的锁被释放。元数据共享读锁和元数据排他锁不能同时存在:长事务里没提交的SELECT一直握着元数据共享读锁,后面的ALTER就拿不到元数据排他锁,语句会一直停在等待状态。

元数据锁的持续时间分三档:

  • 语句结束就释放的叫语句级;
  • 事务提交才释放的叫事务级;
  • 显式持有,直到执行UNLOCK TABLES语句才会释放。

事务里的SELECT如果在事务中执行,会持有事务级的元数据共享读锁:语句跑完不会立刻释放,会一直持有到事务提交,才会被释放。元数据锁不走innodb_lock_wait_timeout,但会受lock_wait_timeout影响,等太久可能超时报错。ALTER拿不到元数据排他锁时,语句会一直等着;你能做的,就是等还一直持有元数据共享读锁的事务提交,或者结束那个会话。

意向锁:IS、IX

意向锁(Intention Lock)是InnoDB里的一种表级锁。它锁的不是整张表的数据,而是表达一个声明:这个事务打算在表里的某些行上,加什么类型的行锁。

分成两种:

  • 意向共享锁(IS):事务打算在某些行上加共享锁(S锁),读行。
  • 意向排他锁(IX):事务打算在某些行上加排他锁(X锁),改行。

意向锁存在的意义,是让显式表锁(比如LOCK TABLES ... WRITE拿到的表级X锁)和行级锁能高效共存。事务要锁行时,先在表级加IS或IX,等于告诉后面想加表级锁的操作:这张表里有人在锁行。后续的表级锁请求就能快速判断是否冲突,不必逐行检查每一行上有没有行锁。

有一点容易误会:普通的SELECT快照读,InnoDB层往往不加IS/IX;SELECT FOR SHARE加IS,update、insert、delete和SELECT FOR UPDATE加IX。

IS和IX之间都兼容。和显式表锁S、X比:IS和S可以共存,IX和S冲突,IS和IX都不能和X共存。面试时画个兼容矩阵就能说清楚。

LOCK TABLES WRITE会同时涉及元数据锁和InnoDB表锁:元数据层加「禁止读写的共享锁」(模式SNRW),InnoDB层加表排他X锁(显式表锁,不是意向锁)。日常DML几乎不会显式加表锁S或X,只在真正锁行时才隐式加意向锁。

行级锁:三种精确语义

行级锁不只是简单的共享和排他,InnoDB还设计了三种精确语义来控制锁的范围。

索引记录在物理存储上是一条链,记录之间有空隙。

  • Next-Key锁锁住记录本身和前面的间隙,是可重复读下默认的记录锁形态。
  • Gap锁只锁间隙,不锁记录,用来防止别的事务往间隙里插行。
  • Record锁只锁记录,不锁间隙,在只读已提交或唯一索引等值点查时会用到。

隔离级别对行锁影响很大:

对比项 只读已提交RC 可重复读RR
普通SELECT 快照读,不加行锁 快照读,不加行锁
当前读加锁范围 多数只锁匹配记录 范围扫描常带间隙
锁释放时机 语句结束释放行锁 事务结束才释放间隙锁
幻读 可能出现 当前读下用间隙锁避免

插入意向锁与隐式锁

注意区分:下面的「插入意向锁」(Insert Intention Lock)和上一节的表级「意向锁」(Intention Lock,IS/IX)是两种不同的锁。英文里都带Intention,但一个是插入时在间隙上等待,一个是表级IS/IX,别混在一块。

INSERT语句有一种特殊的锁:插入意向锁。当插入的目标间隙上已有Gap锁时,插入事务会先加上插入意向锁,然后进入等待。多个事务往同一间隙插入时,它们的插入意向锁不冲突,可以同时等;但它们都要等Gap锁先被释放。所以会出现:读事务握着Gap锁,写事务的INSERT在下面排队等着;两个INSERT在等同一个Gap时,彼此之间不用互相等。

隐式锁是另一个容易被忽略的设计。未提交的update并不立即创建锁对象,而是在聚簇记录上留下trx_id。别的事务来读这条记录时,InnoDB判断trx_id是否活跃,活跃才会转换成显式锁。这种设计省内存,发生冲突时才会创建锁对象。

还有N多的锁

比如自增锁,谓词锁,谓词页锁等等。。。。非常的多,但最关键的,还是我们上面提到的,先从架构入手,然后再逐个击破每个锁。

用架构思维回答面试题

面试被问到锁相关的问题,按三层架构来组织答案会更清晰。

MySQL都有哪些锁?

按三层说:Server层有元数据锁MDL,管表结构能不能动;InnoDB表级有意向锁IS/IX和自增锁;InnoDB行级有共享锁、排他锁,以及Next-Key、Gap、Record三种精确语义,还有插入意向锁和隐式锁。

什么是间隙锁?和Next-Key啥关系?

间隙锁只锁索引记录之间的空隙。Next-Key是记录锁加记录前面的间隙,是可重复读下默认的记录锁形态;Gap是其中的间隙部分。可重复读语境下口头说的行锁,往往指的是Next-Key锁。

可重复读怎么防幻读?

快照读读MVCC版本,不加行锁。在可重复读下,同一事务的快照读共用读视图,别的事务后来提交的插入,快照读一般看不到。当前读会加Next-Key锁和Gap锁,不让别的事务往间隙里插行,从而减少当前读路径上的幻读。只读已提交下,每次快照读可能看到不同的行数,幻读更容易出现。

意向锁IS/IX有啥用?

让显式表锁和行级锁高效共存。事务在真正锁行前先加IS或IX,等于在表级声明「打算在某些行上加S锁或X锁」。后面如果有人要LOCK TABLES拿表级S或X锁,InnoDB看一眼表上的意向锁就能判断会不会冲突,不必扫全表检查每一行的锁状态。

元数据锁是什么?为啥删表会一直等着?

元数据锁保护的是表定义,不是行数据。删表要拿元数据排他锁,独占表定义。只要还有会话一直持有该表的元数据锁,DDL语句就会一直在等待状态等着。特别是未提交的长查询,会一直持有元数据共享读锁,不会释放。

死锁怎么产生?怎么查?

两个事务互相等对方持有的锁,形成环。排查看SHOW ENGINE INNODB STATUS里的LATEST DETECTED DEADLOCK,或performance_schema的data_locks、data_lock_waits。InnoDB检测到环后,会按trx_weight等规则挑一个事务回滚,不完全是「谁改动少就回滚谁」。

SELECT FOR UPDATE锁什么?

当前读,Server层拿元数据共享写锁,InnoDB层加意向排他锁IX,对命中行加排他锁;可重复读下范围条件可能加Next-Key或Gap。未命中唯一索引等值且记录不存在时可能只锁间隙。

为什么ALTER表一直在等?

先看元数据锁这一层。有长事务一直握着这张表的元数据共享读锁,ALTER要的元数据排他锁就拿不到,语句会一直停在等待状态。和行锁等待不同,元数据锁不走innodb_lock_wait_timeout,但受lock_wait_timeout约束,只能等那个事务提交、结束会话,或等到超时。

参考的内容

希望这篇文章可以帮到你。

相关推荐
CodeSheep1 小时前
他俩只靠写代码,登上了胡润财富榜!
前端·后端·程序员
IT_陈寒1 小时前
React状态更新总是慢半拍?你可能忘了这个默认行为
前端·人工智能·后端
candyTong2 小时前
阿里开源 AI Code Review 工具:ocr review 的执行链路解析
javascript·后端·架构
铁皮饭盒2 小时前
TypeBox 比 Zod.js 校验 快10倍, 还兼容AI 工具调用, 他做对了什么?
前端·javascript·后端
倔强的石头_11 小时前
WorkBuddy 上手实战:打造一个可用的本地 AI 工作台
后端
To_OC11 小时前
从一次栈溢出报错说起,我把递归彻底扒明白了
javascript·算法·程序员
苍何16 小时前
Coding 真有质的飞跃?实测下豆包seed 2.1 pro
后端
苍何16 小时前
试了下腾讯 Marvis,回不去了...
后端