news 2026/10/5 3:39:20

MySQL锁机制与死锁排查实战:从原理到生产优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL锁机制与死锁排查实战:从原理到生产优化

做后端开发和数据库运维的同学,几乎都遇到过这种场景:凌晨两点被监控电话吵醒,工单上写着“Deadlock found when trying to get lock; try restarting transaction”,或者某个接口的P99延迟从50毫秒飙到5秒,数据库连接池被占满,业务页面直接卡死。十次里有八次,根因都指向同一个地方——MySQL的锁机制。

锁真不是面试题里“共享锁和排他锁有什么区别”那种死记硬背的东西,它是InnoDB并发控制体系的命脉。理解锁,本质上是在回答一个问题:当多个事务同时抢同一份数据时,系统凭什么保证不出错,又凭什么让尽量多的人能“同时干活”。这个平衡点,就是并发控制的核心矛盾。

这篇文章从一次真实的线上事故讲起,把MySQL锁机制的骨架拆开,讲清楚锁的分类、加锁规则、死锁排查、锁竞争优化,最后落到几套生产环境真正能用的并发优化策略。不管你是刚入门MySQL的新手,还是被线上死锁折磨过的开发,都能从这里拿走可以直接用的东西。

1. 并发控制的核心矛盾:隔离与效率怎么兼得

1.1 一次线上事故引发的思考

去年我接手过一个订单系统的性能优化,现象非常典型:每天晚高峰的时候,订单表的insert和update偶尔会出现大面积阻塞,一个原本几十毫秒的扣库存操作,时不时飙到两三秒,紧接着就是死锁报错。监控面板上能看到Innodb_row_lock_current_waits和Innodb_row_lock_time这两个指标在飙升。

当时第一反应是“是不是某条SQL写得有问题”,但翻了一遍慢查询日志,发现所有SQL执行计划都走了索引,单条语句怎么看都不至于拖垮库。后来把问题固化到一个具体场景里才看清楚:A事务先更新商品库存再把订单状态从未支付改成已支付,B事务先查订单状态再更新库存,两条事务的加锁顺序恰好相反,在并发量上来之后,双方各自持有一把锁等待对方释放,死锁就是这么产生的。

这个案例特别典型,它说明了一个关键点:锁问题的根源往往不是某一单条SQL,而是多个事务之间的资源访问顺序和锁持有时间。MySQL底层是一个很“老实”的引擎,你让它怎么加锁它就怎么加锁,它不会分辨你的业务逻辑是不是合理。数据库本身没错,问题出在业务代码对并发场景的失控。

1.2 锁、MVCC与隔离级别三者的分工

要把锁机制讲透,得先摆清楚InnoDB并发控制的完整拼图。MySQL的并发控制不是靠锁单打独斗,而是“锁 + MVCC(多版本并发控制)+ 事务隔离级别”这三者协作的结果。

事务隔离级别决定了事务能看到什么数据,它由transaction_isolation参数控制,默认是REPEATABLE READ(可重复读,简称RR)。查当前的隔离级别很简单:

SHOW VARIABLES LIKE 'transaction_isolation'; -- 或者 SELECT @@transaction_isolation;

MVCC做的事情,是用undo log生成数据的历史版本,让普通的SELECT查询(快照读)不加锁就能读到一致性快照,这样读操作永远不会阻塞写操作,写操作也不会阻塞读操作。而锁负责的是“当前读”——也就是SELECT ... FOR UPDATE、UPDATE、DELETE这些语句,它们必须读取数据的最新版本,并且要对目标记录加锁,防止其他事务同时修改。

两者之间的关系可以打个比方:MVCC是一套“历史档案系统”,每个人都可以翻阅档案而不影响当下正在发生的修改;锁是一套“实时登记簿”,谁要动当前的数据,谁就得先在登记簿上签字占位。

机制负责内容是否加锁典型场景
MVCC快照读的一致性视图不加锁普通SELECT
锁机制当前读的互斥控制加锁UPDATE、DELETE、FOR UPDATE
隔离级别定义事务间的可见性与锁的启用策略视级别而定全局事务配置

所以定位并发问题的时候,先要分清到底是“读到了不该读的数据”还是“写的时候互相阻塞”。前者多半和MVCC、隔离级别有关,后者基本就是锁的问题。这篇文章后面的内容,全部围绕锁展开。

2. InnoDB锁体系全景拆解

2.1 锁的粒度:表锁、行锁、页面锁

锁的粒度,说直白点就是“一次锁住的范围有多大”。MySQL的存储引擎各自实现不同,MyISAM只有表锁,InnoDB则同时支持表锁和行锁,这也是InnoDB在高并发场景下能碾压MyISAM的根本原因。NDB引擎还有页面锁,但日常开发基本碰不到,知道即可。

表锁(Table Lock)锁住整张表,粒度大、开销小、并发度低。InnoDB里显式的表锁用得不多,LOCK TABLES ... WRITE这种语句一般在做表结构维护或者数据迁移时才会见到。真正在日常运行中常见的表锁是元数据锁和DDL锁,这部分我后面单独讲。

行锁(Row Lock)锁住的是索引记录,粒度小、并发度高,但加锁开销也大。InnoDB的行锁不是直接锁“行数据”,而是锁“索引记录”——这是理解InnoDB锁的一个关键点。如果一张表没有主键,InnoDB会隐式生成一个6字节的row id作为聚簇索引;如果连索引都没有,行锁也就无从谈起,会退化成表锁。

行锁的实现依赖索引这一特性,导致了一个非常容易踩的坑:如果更新语句没有走到索引,InnoDB会对全表记录逐条加锁,从效果上看等于表锁。这也是“明明有行锁,为什么并发一高还是堵死”的最常见原因。

2.2 锁的模式:共享锁、排他锁、意向锁

按模式分,InnoDB锁有两类基础模式:

  • 共享锁(Shared Lock,S锁):读锁,加了S锁的记录,其他事务还能加S锁来读,但不能加X锁来写。
  • 排他锁(Exclusive Lock,X锁):写锁,加了X锁的记录,其他事务无论S锁还是X锁都不能再加。

这两种锁的兼容性可以用一张小表来表示:

锁模式是否兼容S锁是否兼容X锁
S锁兼容不兼容
X锁不兼容不兼容

也就是说,读读不互斥,读写互斥,写写互斥。这套规则是整个并发互斥的基础。日常SQL里,普通SELECT不加锁;SELECT ... LOCK IN SHARE MODE(8.0里也可以用SELECT ... FOR SHARE)加的是S锁;SELECT ... FOR UPDATE、UPDATE、DELETE加的是X锁。

除了S锁和X锁,InnoDB还有一种很特殊的意向锁(Intention Lock)。意向锁是表级别的锁,它不锁任何具体记录,只是标记“这个事务准备在表里的某些行上加锁”。它的作用,是在表级判断“有没有行锁冲突”时省去逐行检查的麻烦。

举个例子:事务A给表里的第100行加了X锁,此时事务B想执行LOCK TABLES ... WRITE锁住整张表,如果不去检查行锁,凭什么知道这张表“已经有行被锁住了”?有了意向锁,A在加行锁之前会先给表加一个意向排他锁(IX),B一看表上已经有IX锁,立刻就知道“这张表有事务在改行”,不用扫描全表行锁。意向锁之间是互相兼容的,它只用来和显式的表锁做冲突判断。

2.3 行锁的三种算法:记录锁、间隙锁、临键锁

行锁虽然叫“行锁”,实际在InnoDB里细分下来有三种算法,这也是“MySQL锁的分类”里最容易被混淆的部分:

  • 记录锁(Record Lock):锁住具体的某一条索引记录。比如WHERE id = 10且id是主键,就直接锁住id=10这一条。
  • 间隙锁(Gap Lock):锁住两个索引记录之间的区间,防止其他事务在这个区间插入新记录。它锁的是一个“范围”而不是具体记录。
  • 临键锁(Next-Key Lock):记录锁+间隙锁的组合。锁住一个左开右闭的区间,比如(5, 10],既锁住记录10,也锁住5到10之间的空隙。

临键锁是InnoDB在REPEATABLE READ隔离级别下的默认行锁算法。它存在的目的,是为了解决幻读问题——也就是事务A两次查询同一范围的数据,第一次查到5条,第二次却变成了6条,多出来的那条就是“幻影记录”。临键锁通过锁住范围内的所有间隙和记录,让别的事务无法插入新记录,从而保证范围查询的一致性。

很多人在理解间隙锁的时候卡住,我提供一个简单的记忆方式:记录锁锁“点”,间隙锁锁“缝”,临键锁锁“点加缝”。间隙锁是范围锁的一大代价——它允许两个事务同时锁住同一个间隙,因为间隙锁之间互相兼容,但它们都会阻塞往这个间隙里插入新记录的新事务。所以一旦业务里频繁出现范围更新,间隙锁很容易成为“隐形的并发杀手”。

2.4 容易被忽视的几种锁:MDL锁、自增锁、插入意向锁

除了上面这些耳朵都听出茧子的锁,生产环境里更要命的往往是那些平时不太被提到的锁。

**元数据锁(Metadata Lock,MDL)**就是其中一个。MySQL从5.5开始引入MDL,目的是保护表结构定义。任何事务执行SQL时,都会先拿MDL读锁;ALTER TABLE改表结构时需要MDL写锁。写锁和读锁互斥,所以一个长事务把持着MDL读锁不放,后面的DDL就一直在“Waiting for table metadata lock”,把整张表的读写全部堵住。这种事故非常常见,症状是数据库看起来“卡死”,SHOW PROCESSLIST里一堆Waiting for table metadata lock,但innodb_row_lock指标却正常。

**自增锁(AUTO-INC Lock)**则是在插入包含自增列的数据时使用的特殊表级锁。它跟普通锁不一样,每次插入都会短暂持有,但长度可以配置。innodb_autoinc_lock_mode=0是传统模式,=1是批量插入时锁表,=2是性能最优但binlog不能混用格式的模式。8.0默认是模式2,配合binlog_format=ROW用没问题。

**插入意向锁(Insert Intention Lock)**是间隙锁的“温柔版本”。当多个事务想往同一个间隙插入记录时,它们会先在间隙上申请插入意向锁,互相不阻塞,只是在插入前需要等待已有的间隙锁释放。所以“高并发下同一张表插入卡顿”,往往不是插入意向锁互斥,而是有事务拿间隙锁堵住了插入区间——比如前面有事务执行了大范围查询且隔离级别是RR。

2.5 锁信息去哪里查

理论讲完,上实操。定位锁问题,核心就两张表:

  • performance_schema.data_locks:当前持有的锁详情,8.0用这个视图替代了老版本的information_schema.INNODB_TRX和INNODB_LOCKS。
  • information_schema.INNODB_TRX:当前运行中的事务列表。

一条常用查询是:

SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query, trx_rows_locked, trx_rows_modified FROM information_schema.INNODB_TRX;

再看看锁等待的明细:

SELECT engine_transaction_id, object_name, index_name, lock_type, lock_mode, lock_status, lock_data FROM performance_schema.data_locks;

这两条SQL配合使用,能迅速定位到“谁在等谁”。更直观的办法是用SHOW ENGINE INNODB STATUS\G里的LATEST DETECTED DEADLOCK和TRANSACTIONS段落,能看到最近一次死锁的事务和锁信息。这个命令非常有用,下面第4部分会专门教怎么读它的输出。

3. 加锁规则与并发控制实现机制

3.1 加锁的基本规则:两阶段锁与当前读

InnoDB的加锁遵循两阶段锁协议:事务中加锁操作分为扩展阶段和收缩阶段,锁只能增加不能提前释放,所有锁在事务提交或回滚时统一释放。这意味着锁的持有时间等于事务从加锁到提交之间的全部时间。很多锁等待问题,根子不在数据库,而在事务里“加锁之后还磨磨蹭蹭做了很多别的事”,把锁持有时间拉长了。

举个极端例子:一个事务里执行了两次更新,中间夹了一次RPC调用外部接口,RPC超时3秒,这3秒里事务握着所有行锁不释放,后面的更新全部排队。这类问题靠调数据库参数是治不好的,必须从应用层改代码——把耗时的外部调用挪到事务外面。

“当前读”需要加锁,“快照读”不加锁,这个区别前面已经提过。实际开发中常见的误用是:用SELECT查出来的数据直接参与后续更新,中间没有加锁保护,导致结果出现偏差。这时候应该用SELECT ... FOR UPDATE加上排他锁,把判断和修改做成一个原子的临界区。

3.2 不同隔离级别下的加锁差异

隔离级别会影响锁的“激进程度”,这是并发性能优化的一个隐性杠杆。拿REPEATABLE READ和READ COMMITTED(读已提交,RC)对比:

  • RR级别下,普通查询走快照读,但UPDATE、DELETE、FOR UPDATE这类当前读会默认使用临键锁,锁住扫描范围的所有记录和间隙。间隙锁的存在,让“范围更新”变成一场灾难。
  • RC级别下,当前读只使用记录锁,不启用间隙锁,因此并发度更高。这也是为什么很多互联网公司把隔离级别从RR调成RC来提升性能。

代价是RC的“可重复读”能力被削弱,同一事务里两次读可能看到不同的数据。不过在大部分业务里,快照读保证一致性已经够用,RR带来的间隙锁阻塞反而得不偿失。我见过的生产库,绝大多数是RR和RC并存:默认保持RR,对某个特定高并发业务库单独设置RC。

修改隔离级别要千万注意:它不是Session级别的玩具,而是会影响所有并发事务的全局决策。改之前要评估业务里有没有依赖可重复读语义的代码,否则数据一致性会出问题。

3.3 从一条SQL看加锁范围

加锁范围的分析是理解锁机制的“高等数学”,也是面试里经常被问到的“一条update会锁几行”。这里看两个案例。

第一,等值查询只有唯一索引或主键命中时,加的是记录锁。比如:

UPDATE t SET status = 1 WHERE id = 500;

如果id是主键,这条语句只会锁住id=500那一个索引叶子节点,其他行的插入更新都不受影响。

第二,范围查询或索引筛选不够精确时,加的是临键锁:

UPDATE t SET status = 1 WHERE create_time < '2024-06-01 00:00:00';

如果create_time上有二级索引,InnoDB不仅会锁住所有满足条件的记录,还会锁住第一个不满足条件记录之前的间隙,防止插入新的满足条件的记录。如果这条SQL扫描了10万行,那10万行索引记录加间隙全部会被锁住,其他事务想往这个范围插数据,统统阻塞。

这类查询通常在“批量更新”场景出现,全表扫描的批量更新是生产环境锁风暴的源头。优化方向是拆分批次,每批只更新少量数据,并确保WHERE条件能走索引。

4. 实测:模拟锁等待与死锁,并读懂现场

4.1 环境准备与造数

理论再复杂,不如手动复现一次。我准备一张简单的订单表来说明:

CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, sku_id INT NOT NULL, user_id INT NOT NULL, status TINYINT NOT NULL DEFAULT 0, version INT NOT NULL DEFAULT 0, INDEX idx_sku (sku_id), INDEX idx_status (status) ) ENGINE=InnoDB; INSERT INTO t_order (order_no, sku_id, user_id, status) VALUES ('O1001', 2088, 1, 0), ('O1002', 2088, 2, 1), ('O1003', 3088, 3, 0), ('O1004', 4088, 4, 2), ('O1005', 2088, 5, 0);

4.2 复现锁等待

开两个会话,session A执行:

BEGIN; SELECT * FROM t_order WHERE sku_id = 2088 FOR UPDATE;

此时session A在idx_sku索引上锁住了sku_id=2088对应的多条记录。session B再执行:

BEGIN; UPDATE t_order SET status = 1 WHERE sku_id = 2088;

正常来讲,B会一直卡住,直到A提交或超时。查看B的状态:

SELECT trx_id, trx_state, trx_wait_started, trx_mysql_thread_id FROM information_schema.INNODB_TRX;

B的事务状态会显示LOCK WAIT,等待超时时间由innodb_lock_wait_timeout控制,默认50秒。等不住的时候,会直接报Lock wait timeout exceeded; try restarting transaction。

这个复现过程说明了一个很重要的优化思路:如果你能准确知道事务改了哪些行,就可以考虑缩短锁范围,或者让不同事务操作不同sku的数据,避免把多个用户的操作压在同一条数据上。

4.3 复现死锁

再来模拟经典死锁AB-BA场景。接上面两张表,我们开两个事务,都同时操作相同的两行数据但顺序相反。

session A:

BEGIN; UPDATE t_order SET status = 2 WHERE id = 1; -- 稍等一下,不要提交

session B:

BEGIN; UPDATE t_order SET status = 2 WHERE id = 2; -- 稍等一下,不要提交

然后session A继续执行:

UPDATE t_order SET status = 2 WHERE id = 2; -- 会发生锁等待

session B继续执行:

UPDATE t_order SET status = 2 WHERE id = 1; -- 发生死锁,MySQL选择回滚其中一个事务

此时MySQL会立即检测到死锁,让其中一方回滚,报错字符串是Deadlock found when trying to get lock; try restarting transaction。这就是开头工单里那一行的由来。

死锁检测的原理是InnoDB维护了一个等待图(Wait-for Graph),当事务之间的锁等待形成环时,立刻选中代价最小的事务作为牺牲者回滚。所以InnoDB的死锁不是靠超时“等”出来的,而是秒级检测出来的。

4.4 死锁日志怎么读

实战里我们碰到的死锁,绝大多数是从日志反推原因的。死锁后执行:

SHOW ENGINE INNODB STATUS\G

找到LATEST DETECTED DEADLOCK段落,逐行解读几个关键字段:

  • TRANSACTION后的十六进制数:事务ID,可用来反查应用日志里的连接来源。
  • MySQL thread id:是哪个连接会话,可以顺着查到具体是哪个应用实例。
  • WAITING FOR THIS LOCK TO BE GRANTED:等待的锁。
  • LOCK HELD BY THIS:持有的锁。
  • lock_mode X locks rec but not gap:排他锁且是记录锁(不是间隙锁)。
  • index PRIMARY of table xxx.t_order:锁在哪个表的哪条索引上。

一个典型的死锁日志会交替出现两个事务,一个“持有”一个“等待”,结尾出现WE ROLL BACK TRANSACTION字样。生产上分析死锁的正确姿势是:把日志里的thread id和应用日志对起来,还原两条SQL的执行顺序,然后看是不是加锁顺序相反——是的话,调整业务代码的加锁顺序即可。

5. 优化策略:把并发性能压出来

5.1 从SQL层面减少锁范围

见过太多慢和堵的问题,根本原因就是SQL扫描范围太大。优化的第一板斧永远是:让WHERE条件走索引且走窄索引。

比如上面那张表,如果经常要用user_id和status做条件,就应该建联合索引(user_id, status),而不是单独两个单列索引。联合索引能显著缩小加锁的记录数。反之,如果UPDATE语句的WHERE里带了一个OR条件,或者对字段做了函数运算(比如WHERE DATE(create_time) = '2024-06-01'),索引就会失效,扫描范围瞬间扩大,锁的范围也跟着变大。

此外,尽量避免SELECT * FOR UPDATE这种全列加锁的写法,只要能确认主键,直接WHERE id IN (...)分小批操作。

5.2 从索引层面细化锁粒度

行锁的本质是索引记录锁。索引设计直接决定“一行更新会锁住多少行”。最典型的问题是二级索引回表——更新语句走了二级索引,InnoDB会同时在二级索引记录和对应的聚簇索引记录上加锁。如果二级索引的区分度很低(比如只有几个值),一个条件下会命中大量重复索引记录,锁的范围会被放大器放大。

还有个容易忽略的细节是覆盖索引下锁会不会更小。答案是:如果SQL只需要二级索引上的列就能完成判断,不需要回表,InnoDB有时可以只锁二级索引而不锁聚簇索引;但一旦需要回表读其他列,两条索引的锁都要加。因此适当把高频更新涉及的小字段放进二级索引里,既提升查询性能又能减少锁冲突。

5.3 从事务层面压缩锁持有时间

SQL优化做到位之后,锁竞争的最大剩余来源就是事务太长。压缩锁持有时间的几个实操手段:

  • 事务里只放必要的SQL,尽量把读操作放到事务外先查出结果。
  • 大事务拆小事务,比如批量更新1万行,拆成每批500行,批间sleep或立即提交。
  • 不要在一个事务里调用外部接口或等待用户输入,这类IO操作是锁超时的头号元凶。
  • 减少交互式事务——很多API框架里默认开启了事务,但事务范围包含了接口的网络传输时间,特别危险。

5.4 参数调优的关键细节

MySQL的锁相关参数里,最该关注的有三个:

innodb_lock_wait_timeout = 50 -- 锁等待超时时间,单位秒,可适当调小,避免SQL长时间霸占 innodb_deadlock_detect = ON -- 死锁检测开关,默认开启 innodb_autoinc_lock_mode = 2 -- 自增锁模式,建议保持默认

一个值得讨论的取舍是innodb_lock_wait_timeout。默认50秒太长,线上接口等50秒早就超时了;调太短又可能误伤正常的短暂排队。经验值一般是5到10秒,配合应用层的重试机制。另外一个冷门但有用的点:高并发秒杀场景下,可以把innodb_deadlock_detect关掉,用innodb_lock_wait_timeout兜底。因为死锁检测在高并发下会消耗大量的CPU去遍历等待图,关掉后依靠超时回滚反而能提高吞吐。这个操作比较激进,适合压测验证过的特定场景,普通业务别乱关。

5.5 业务层降级方案

数据库锁优化的天花板,是单库单表的并发能力。到这一步还顶不住,就该在业务层分流了:

  • 热点行拆分:把同一个商品SKU的库存拆成多行,比如50行库存明细,每行存一部分,更新时随机选一行。这是秒杀系统的经典做法。
  • 异步化削峰:更新操作投递到消息队列,后端串行消费,消除峰值并发。
  • 分库分表:把锁竞争分散到不同实例和不同表上。这是最彻底的方案,但对业务改造影响最大。

6. 常见问题速查表与经验技巧

6.1 高频问题排查速查表

现象可能原因排查手段快速处理
UPDATE一直卡住直到超时行锁被其他长事务持有SHOW PROCESSLIST看Waiting for lock,查INNODB_TRX找到持锁事务,kill或等提交
表结构修改卡死MDL锁被长SELECT阻塞SHOW PROCESSLIST看Waiting for table metadata lock找到长查询,kill或等它结束
插入很慢,间隙锁阻塞RR隔离级别的范围锁查data_locks,看是否有Gap锁换成RC级别,或缩小范围
死锁频繁事务加锁顺序不一致SHOW ENGINE INNODB STATUS看死锁日志统一加锁顺序,缩短事务
高并发下CPU飙高死锁检测遍历等待图压测确认后关掉innodb_deadlock_detect压测验证后调参

6.2 独家避坑经验

我踩过几次坑之后整理了几条经验,不一定写在官方文档里,但实战价值很高。

第一,SELECT ... FOR UPDATE不要轻易用于“存在性判断”。很多同学喜欢先查一下“这条数据存不存在”,存在就update,不存在就insert。这个操作在并发下会出现诡异的现象:两个事务同时发现数据不存在,同时插入,然后其中一个报主键冲突或死锁。正确姿势是直接对唯一索引执行INSERT ... ON DUPLICATE KEY UPDATE,让数据库自己处理冲突。

第二,批量更新一定要留“后门”。线上秒级大批量更新时,如果事务没提交完,想停都停不下来,只能眼睁睁看着锁把业务拖死。我的习惯是给批量任务加一个“开关表”,任务每处理一批就检查开关状态,发现要停就立刻提交并退出。这比硬kill进程优雅得多。

第三,锁竞争问题的排查顺序要固定。先看SHOW PROCESSLIST确认卡在哪里,再看INNODB_TRX找出长事务,最后看data_locks确认具体锁。顺序反过来的话,很容易被海量锁信息淹没,几十个事务几百行锁记录,看半天也看不出谁堵谁。

6.3 面试高频问题速答

把“MySQL锁的分类”这种常见面试题串一下,拿去直接背:

  • 按粒度:表锁、行锁、页面锁。
  • 按模式:共享锁S、排他锁X、意向锁IX/IS。
  • 按算法:记录锁、间隙锁、临键锁。
  • 按用途:元数据锁MDL、自增锁、插入意向锁。

面试里加一道“一条UPDATE语句会加哪些锁”的进阶题,回答框架是:先看隔离级别,RR下默认加临键锁;再看条件是否走索引,走主键就是记录锁,走二级索引还要锁二级索引记录和对应聚簇索引;范围查询会加间隙锁;最后别忘了表级的意向锁。

我个人在排查了这么多锁问题之后的体会是:锁不是设计出来的,是被业务场景逼出来的。真正的高手不是背熟锁分类,而是在写SQL和设计事务时就能预判“这段代码在高并发下会不会成为锁的风暴中心”。理解了锁的底层逻辑,再回来看那些慢查询、死锁、连接池打满的告警,会发现每条告警背后都有一个清晰的故事。

最后再分享一个小技巧:维护一个“锁问题复盘文档”,每次线上死锁或锁等待,都把SHOW ENGINE INNODB STATUS的输出和当时的SQL存下来,标注根因和解决方式。攒上几个月,你会发现自己对并发控制的理解上一个台阶,因为数据库的锁行为,是最诚实的并发教科书。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 3:39:15

openrig开源模拟赛车座舱:铝型材DIY搭建与调校全指南

openrig 这个项目&#xff0c;严格来说不是某一个作者开一个仓库发一套图纸那么简单。在模拟赛车这个圈子里&#xff0c;rig 指的是整套驾驶舱设备&#xff0c;包括座椅、转向机、踏板、显示器和整体框架&#xff1b;open 则意味着从 CAD 模型、BOM 清单到安装步骤全部开放&…

作者头像 李华
网站建设 2026/10/5 3:38:47

Java局域网聊天室系统设计与实现:从Socket编程到线程安全

简介&#xff1a;这是一份面向毕业设计与课程设计的Java局域网聊天室系统完整资料&#xff0c;适合计算机相关专业学生和初学Java网络编程的开发者使用。内容包含ChatClient与ChatServer两部分的源代码、配套论文以及工程配置文件&#xff0c;覆盖Socket通信、多线程、界面交互…

作者头像 李华
网站建设 2026/10/5 3:36:47

SPSS主成分分析全攻略:操作步骤、结果解读与因子分析区别

做问卷分析的人&#xff0c;电脑里基本都有一个SPSS&#xff0c;而SPSS里有一道绕不过去的坎&#xff0c;叫主成分分析。我见过不少同学在第一步就卡住&#xff1a;“主成分分析去哪里找&#xff1f;Analyze菜单里怎么没有PCA这个选项&#xff1f;”然后有人就会告诉他&#xf…

作者头像 李华
网站建设 2026/10/5 3:36:32

插件加载失败排查:从契约到Web Boot的实战解析

做开发这些年&#xff0c;我几乎天天和各种plugins打交道。嵌入式IDE里的调试扩展、CI/CD平台上的流水线插件、音乐播放器里的第三方音源模块&#xff0c;表面上是完全不同的东西&#xff0c;底层却是同一套逻辑&#xff1a;宿主程序暴露接口&#xff0c;外部模块按约定接入&am…

作者头像 李华
网站建设 2026/10/5 3:36:11

从CSV导入到筛选打印:Excel三步提升数据处理效率

每天重复做同一种Excel操作&#xff0c;做到想吐的人应该不少。我之前带过一个做库存管理的朋友&#xff0c;他每天从系统里导出好几份CSV文件&#xff0c;一份一份复制粘贴进Excel&#xff0c;然后按客户订单一行一行找需要备货的条目&#xff0c;找到了再按CtrlP打印。整套动…

作者头像 李华
网站建设 2026/10/5 3:35:44

ZYNQ EMIO调试UART完整实验:从Vivado配置到Linux设备树

把ZYNQ的UART1从固定的MIO引脚挪到EMIO&#xff0c;再通过PL逻辑自定义引脚引出——这个操作我在一个多路串口项目里折腾了整整一天才完全跑通。很多资料只讲了MIO和EMIO的区别&#xff0c;却没有说清楚在Vivado里到底怎么配、约束怎么下、Linux下怎么变成ttyS。这篇文章就把我…

作者头像 李华