☰
MySQL锁机制全解:从全局锁到行锁,死锁排查与优化
2026/10/8 3:06:05 网站建设 项目流程

开篇先说一个我反复遇到的场景:线上某个订单接口突然变慢,慢日志里一堆UPDATE语句卡在Waiting for lock,Show processlist里看不到死锁,就是单纯地互相等待。大部分人第一反应是"代码写错了",但查了半天发现业务逻辑没毛病,最后定位到是锁表或者锁范围过大。这种事遇得多了,我就把 MySQL 锁相关的知识系统地整理了一遍,从全局锁到行锁,从原理到排查手段,包括一个非常隐蔽的"二级索引回表锁定交叉窗口"问题,都总结在这篇文章里。

这个话题适合所有跟数据库打交道的人:刚入门的新手需要知道锁的分类和基本概念,后端开发要懂得如何避免锁等待和死锁,DBA 和架构师则需要一套完整的排查和优化方法论。这篇文章没有那些花哨的东西,全是实际工作中能直接用上的经验和判断方法。

1. 锁解决的是"并发下的账本错乱"问题

1.1 没有锁会发生什么

很多初学者觉得锁是个"性能杀手",一提到锁就头疼,甚至想尽办法避免它。这种心态其实不对,锁本质上是在保护数据的一致性,它解决的问题非常实在。

想象一个最简单的场景:商品库存表,一行记录stock = 100。两个用户同时下单,各扣 1 件库存。如果没有锁,两个事务同时读到100,各自算出99,最后写回去的也是99,实际卖出 2 件,库存只减了 1 件,这就叫"丢失更新"。在真实的并发系统里,这种数据错乱是灾难性的。

所以 InnoDB 引入锁的核心理由就一句话:在并发读写时保证数据的正确性。它的工作方式是,当一个事务要修改某条记录时,先在这条记录上"盖个章",其他事务看到这个章就得等,直到盖章的事务提交或回滚。

1.2 事务隔离级别和锁的关系

锁不是孤立存在的,它和事务隔离级别是配合工作的。MySQL 支持四种隔离级别:读未提交(READ UNCOMMITTED)、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)、串行化(SERIALIZABLE)。不同隔离级别下,锁的类型、加锁时机、释放时机都不一样。

在 InnoDB 里,默认隔离级别是可重复读(RR)。很多文章讲"MVCC 实现了可重复读",听起来好像和锁完全没关系,但实际并不是这样。MVCC 解决的是普通SELECT的快照读问题,让读操作不阻塞写操作;但对于UPDATE、DELETE、SELECT ... FOR UPDATE这种当前读操作,必须走锁机制来保证每次都读到最新版本。

举个例子,在 RR 隔离级别下执行:

SELECT * FROM orders WHERE order_id = 100 FOR UPDATE;

这个语句会真的给order_id = 100这行记录加一个排他锁,其他事务想改这行就必须等。而在读已提交(RC)级别下,同样的语句加锁期间如果事务还没结束,其他事务也能对这条记录进行修改,这就可能产生不可重复读的问题。

所以锁和隔离级别是"一体两面"的关系:隔离级别定义了事务之间的隔离程度,锁则是实现这种隔离程度的具体手段。

1.3 InnoDB 锁的粒度选择逻辑

锁可以加在不同粒度上:整张表、某个页、某条记录。粒度越大,加锁开销小,但并发能力低;粒度越小,并发能力高,但锁管理和检查的成本也高。

MyISAM 只支持表锁,所以同一时间只能有一个事务写某张表,并发写入性能极差。InnoDB 之所以成为主流,核心就是支持行级锁,让不同事务可以同时修改同一张表里的不同记录,并发能力大幅提升。

但 InnoDB 也没有完全抛弃表锁,它用了一种叫"意向锁"(Intention Lock)的机制来协调表锁和行锁的关系:事务想锁某个行之前,必须先给表加一个意向锁。意向锁分意向共享锁(IS)和意向排他锁(IX),它们之间是兼容的,但和真正的表级排他锁(X)冲突。这样做的好处是,当一个事务想锁整张表时,可以快速判断出有没有其他事务在锁其中的行,不用逐行检查。

2. 全局锁、表锁与元数据锁:被低估的隐性阻塞源

2.1 全局锁:全库只读的"大动作"

全局锁是整个 MySQL 实例级别的锁,最典型的操作是:

FLUSH TABLES WITH READ LOCK;

这条命令会把所有表都锁定为只读状态,任何写入操作都会被阻塞,直到执行UNLOCK TABLES释放。

全局锁最大的用途是保证备份的一致性。以前用mysqldump做个物理备份或者逻辑备份时,如果不加全局只读锁,备份过程中数据可能被修改,导出的数据就不一致了。

不过实际生产环境里,我很少直接用FLUSH TABLES WITH READ LOCK,因为影响面太大了——一条命令下去,整个库的写入全部卡住。现在的主流做法是使用 InnoDB 的mysqldump --single-transaction,它利用 MVCC 在单个事务里做快照备份,不需要锁全库,备份过程中业务照常写入。这个命令已经能覆盖绝大多数备份场景,全局锁基本退化为某些特定运维操作(比如一些 MyISAM 表备份、数据迁移等)才会使用。

先说结论:能不用全局锁就不用,它的并存场景极其受限,代价却是整个数据库的写入停顿。

2.2 表级锁:LOCK TABLES 与显式锁的坑

表锁是最古老的锁类型,在 MyISAM 时代是唯一选择。InnoDB 也支持表锁,但日常业务基本不会主动用。

LOCK TABLES orders WRITE; -- 做一些需要独占表的操作 UNLOCK TABLES;

LOCK TABLES ... WRITE执行后,其他事务无法读写这张表。我自己在实际使用中最常遇到的问题是:InnoDB 下LOCK TABLES的范围比你想象的大,它除了锁表以外,还会把当前会话里所有已经打开的表都锁住,容易引发不可预知的阻塞。所以现在运营同学或开发同学偶尔提出"我要手动锁一下某张表做数据修复",我的建议都是尽量改用ALTER TABLE在线操作或其他更细的方案。

在 MyISAM 引擎里,表锁还有一个隐藏问题:写锁优先级高于读锁。这意味着如果一张 MyISAM 表的写入很频繁,读请求会被大量积压。这也是为什么新业务我都直接劝用 InnoDB,省得后面还要处理这种锁优先级导致的性能问题。

2.3 MDL 元数据锁:最容易忽略的"隐形势力"

MDL(Metadata Lock)是 MySQL 5.5 开始引入的锁类型,它锁的不是数据,而是表结构。任何 DDL 操作(比如ALTER TABLE、DROP TABLE)都需要获取 MDL 排他锁,而任何 DML 操作(增删改查)都需要 MDL 共享锁。它们之间的兼容性是:共享锁之间兼容,排他锁和共享锁互斥。

这里有个非常经典的线上事故场景:

  1. 某天下午,业务侧发了一个ALTER TABLE orders ADD INDEX idx_status(status),打算给大表加个索引。
  2. 由于这张表数据量大,ALTER TABLE执行时间较长,一直持有 MDL 排他锁。
  3. 此时如果有几个慢查询先持有了 MDL 共享锁,DDL 就会排队等待。
  4. 更麻烦的是,DDL 一旦开始排队,后续所有新的SELECT、UPDATE也会因为需要 MDL 共享锁而全部排队。这个叫"MDL 排队传染"。

我见过一张只有 2GB 的表,因为一个ALTER TABLE卡住,导致整张表的所有查询和更新全部阻塞,业务方报过来的时候已经堆了几百个连接。排查方法很简单,show processlist里看到大量Waiting for table metadata lock,基本就是 MDL 的问题。处理方式一般是找到阻塞源(那条 DDL 或持锁的长事务)并 kill 掉,让后续请求恢复。

所以关于 MDL,我自己的经验是两条:

  • DDL 尽量安排在低峰期执行;
  • 先用SHOW PROCESSLIST确认没有长事务在跑,再执行ALTER TABLE,最好配合pt-osc这类在线变更工具,降低 MDL 锁占用的时间窗口。

3. 行锁的精细世界:记录锁、间隙锁与临键锁

3.1 记录锁(Record Lock)

记录锁是最基础的行锁,它就是锁住索引上的一条具体记录。注意关键词:索引。InnoDB 的行锁是加在索引上的,不是直接加在表的行记录上的。如果一张表没有显式定义主键,InnoDB 也会隐式创建一个聚簇索引,行锁最终都落在某个索引项上。

举个实际例子,假设表结构如下:

CREATE TABLE orders ( id INT PRIMARY KEY, order_no VARCHAR(32) UNIQUE, status TINYINT, amount DECIMAL(10,2) );

事务 A 执行:

UPDATE orders SET amount = 100 WHERE id = 10;

此时 InnoDB 会在主键索引id = 10这个索引项上加上排他记录锁。其他事务想更新id = 10这行就会被阻塞,但更新id = 11完全不受影响。

这是 MySQL 并发性能的重要保障:不同行的操作互不干扰。

3.2 间隙锁(Gap Lock)与幻读

再往深一层,RR 隔离级别下,只锁一条记录是不够的。假设有一个事务要执行:

SELECT * FROM orders WHERE status = 1 FOR UPDATE;

事务先查到了一批status = 1的记录并锁住了它们。但如果另一个事务在这个事务提交之前,插入了一条新记录,恰好status也是 1,会发生什么?第一个事务再次执行同样的查询时,会看到一条之前不存在的记录——这就是幻读。

为了在 RR 级别下阻止幻读,InnoDB 引入了间隙锁。间隙锁锁的不是某条记录,而是索引结构中两个值之间的"空档"。比如表中id现有 10、20、30 三条记录,间隙锁可以锁住(10, 20)这个区间。其他事务往这个区间插入数据就会阻塞。

间隙锁是 RR 隔离级别防幻读的核心机制,但它也是很多锁等待问题的来源。一个常见的踩坑案例是:

UPDATE orders SET amount = 0 WHERE amount >= 1000 AND amount <= 5000;

如果amount上有索引,这条语句不仅会锁住满足条件的行,还会锁住这些行之间的所有间隙。如果业务频繁往这个区间插入新订单,插入操作全都会卡住,直到这个事务提交。加锁范围比开发者预想的大得多,这是新手最容易忽略的点。

3.3 临键锁(Next-Key Lock)

临键锁可以理解为"记录锁 + 间隙锁"的组合,锁定的范围是左开右闭的区间。比如索引值有 10、20、30,临键锁可能锁住的是(10, 20],意味着锁住了 20 这条记录本身,也锁住了 10 到 20 之间的间隙。

在 RR 隔离级别下,InnoDB 默认对普通索引的等值和范围查询都使用临键锁。这也是为什么很多锁等待事故不是发生在更新同一条记录上,而是发生在"更新了相邻范围的记录"这个场景。

这里要注意,RC(读已提交)隔离级别下,InnoDB 只使用记录锁,不使用间隙锁和临键锁。这也是为什么不少高并发团队会把隔离级别从 RR 改成 RC,核心目的之一就是减少间隙锁带来的锁冲突。代价是需要自己在业务层面处理幻读问题,或者接受一定程度的一致性妥协。具体怎么取舍,后面第 6 节再展开。

3.4 插入意向锁(Insert Intention Lock)

插入意向锁是一种特殊的间隙锁,它表示一个事务想在某个间隙里插入记录,但还没真正插入。多个插入意向锁之间是互相兼容的,这意味着不同事务可以同时想往同一个间隙里插入数据,并不互相阻塞;但它们与间隙锁是互斥的——一个事务持有间隙锁时,其他事务的插入操作只能等待。

举一个我踩过坑的场景:某个订单表经常有批量插入操作,业务方升级代码后突然频繁报Lock wait timeout exceeded。排查后发现是一个定时任务用SELECT ... FOR UPDATE扫了一段范围的记录,加了间隙锁,而主业务在这个范围里做批量插入,全部被间隙锁挡住。解决办法是把定时任务改成只锁具体的记录(比如加个LIMIT或者改成主键等值查询),插入操作很快就顺畅了。

这四种行锁类型的关系和差异,我用一张表总结:

锁类型锁对象典型场景兼容性
记录锁(Record Lock)具体的一条索引记录主键或唯一键等值更新不同记录互不影响
间隙锁(Gap Lock)索引值之间的空档RR 下范围查询防幻读与其他间隙锁兼容,与插入意向锁冲突
临键锁(Next-Key Lock)记录锁 + 间隙锁RR 下范围更新、普通索引扫描左开右闭区间,锁冲突高发区
插入意向锁(Insert Intention Lock)待插入的间隙向某间隙插入新记录多个插入意向锁互相兼容,与间隙锁冲突

4. 二级索引回表锁定的"交叉窗口":一个非常隐蔽的死锁源头

4.1 正常加锁路径是怎样的

前面提到 InnoDB 的行锁加在索引上,但真实场景中很多更新语句并不是直接命中主键索引的,而是通过二级索引去查找数据,再回表到主键索引。

举个例子,表结构还是上面那个orders表,假设order_no上有唯一索引,事务执行:

UPDATE orders SET amount = 100 WHERE order_no = '20250101001';

实际的加锁路径是:

  1. 先通过二级索引order_no = '20250101001'定位到索引项,在二级索引上加锁;
  2. 然后回表,在主键索引上找到对应行,在主键索引记录上加锁。

这两个操作之间存在着一个时间窗口,而正是这个窗口,藏着一个容易形成交叉死锁的隐患。

4.2 怎么构造一个交叉窗口的案例

我构造一个真实复现过的场景。假设表里有两行数据:

id = 1, order_no = 'A', status = 1 id = 2, order_no = 'B', status = 1

事务 1 执行:

UPDATE orders SET status = 2 WHERE order_no = 'A';

事务 2 执行:

UPDATE orders SET status = 2 WHERE order_no = 'B';

按直觉来看,这两个事务操作的是不同的行,应该互不相干。但实际上它们可能死锁,原因是这样的:

  • 事务 1 先给二级索引order_no = 'A'加锁,回表给主键id = 1加锁;
  • 事务 2 先给二级索引order_no = 'B'加锁,回表给主键id = 2加锁;
  • 如果加锁顺序完全一致(先二级索引后主键),不会出问题;
  • 但 InnoDB 内部加锁并不是严格按照"所有事务同一顺序"执行的。在某些条件下,事务 1 在二级索引上加完锁、还没回表锁主键时,事务 2 可能已经锁住了主键id = 2,而事务 1 接下来要锁主键id = 2时发现被事务 2 持有;事务 2 回表时可能又需要访问某些二级索引项,而该索引项已被事务 1 持有。

于是形成一个闭环:事务 1 等事务 2 释放主键锁,事务 2 等事务 1 释放二级索引锁。这就是搜索热词里提到的"通过二级索引更新时,先锁二级索引项,再回表锁主键,这个时间窗口容易形成交叉"。

这类死锁非常隐蔽,因为业务逻辑看起来并发量很低,甚至只是更新不同的行,但它就是会偶发出现。

4.3 规避思路

针对这个交叉窗口问题,我总结了几条可落地的规避策略:

第一,等值更新优先走主键。如果业务上能拿到主键 ID,直接用主键作为 WHERE 条件,只加主键索引上的记录锁,完全不经历"二级索引加锁 + 回表加锁"这个两阶段过程,交叉窗口就不存在了。

第二,确保二级索引是唯一的且查询能命中。等值查询走唯一索引时,加锁范围通常较小,交叉概率低。如果是普通二级索引,范围会放大,潜在冲突面也变大。

第三,缩短回表窗口时间。回表窗口本质上就是事务持有二级索引锁到主键锁之间的间隔,如果能把这个间隔压到最短,交叉的概率自然下降。具体手段包括:避免在更新语句中做耗时操作、保持字段简单、减少不必要的连接查询。

第四,必要时使用覆盖索引。如果更新语句的字段都在二级索引里,InnoDB 在某些情况下可以减少回表加锁的需求,但这一点依赖版本和执行计划,不要盲目依赖。

这里补充一句:死锁出现后 InnoDB 会自动回滚其中一个事务,数据库本身不会挂掉,但业务方会收到死锁异常,如果不重试,用户体验就是"这次操作失败了"。所以除了尽量减少死锁发生,应用层对死锁异常做一定的重试机制也是必要的兜底。

5. 死锁与锁等待:从 show engine innodb status 到锁监控

5.1 Lock wait timeout exceeded 是哪个参数

MySQL 的锁等待超时时间由参数innodb_lock_wait_timeout控制,默认值是 50 秒。也就是说,一个事务等待另一个事务释放锁超过 50 秒,InnoDB 会主动放弃并抛错。

50 秒这个默认值,在生产环境里其实偏长。对用户来说,一个接口超过 5 秒没返回就已经很难受了,根本等不到 50 秒。对 DBA 来说,被锁住的事务长时间悬挂,还会拖住一堆后续请求,形成雪崩。我通常建议把innodb_lock_wait_timeout调小到 5~10 秒,让事务快速失败,应用层收到错误后可以快速重试或降级,而不是傻等。

查看当前值:

SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

修改(以 5 秒为例,重启后失效,需要配合配置文件持久化):

SET GLOBAL innodb_lock_wait_timeout = 5;

5.2 死锁日志怎么看

出现死锁时,InnoDB 会把死锁信息记录到错误日志里。查看最近一次死锁详情的命令:

SHOW ENGINE INNODB STATUS\G

重点看输出中的LATEST DETECTED DEADLOCK部分,它会清晰地列出两个事务分别持有什么锁、在等待什么锁。我摘一段典型的输出格式,内容做了简化,方便说明:

------------------------ LATEST DETECTED DEADLOCK ------------------------ *** (1) TRANSACTION: TRANSACTION 1001, ACTIVE 2 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136 WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 18 page no 3 n bits 72 index PRIMARY of table `test`.`orders` *** (2) TRANSACTION: TRANSACTION 1002, ACTIVE 1 sec HOLDING THE LOCK: RECORD LOCKS space id 18 page no 3 n bits 72 index PRIMARY of table `test`.`orders` WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 18 page no 3 n bits 72 index idx_order_no of table `test`.`orders`

日志虽然看起来有点绕,但核心信息就两点:

  • 事务 1001 正在等待一个主键索引上的锁;
  • 事务 1002 持有这个主键锁,自己在等一个二级索引锁;而这个二级索引锁恰好被事务 1001 持有。

看到这个结构,基本就可以确认是第 4 节说的"二级索引回表交叉窗口"问题。如果两个事务都在等主键锁,那就更像是在抢同一行。

5.3 用 performance_schema 定位锁等待源头

死锁日志只能看到"过去"的信息,如果想实时判断当前谁阻塞了谁,比如抓到一堆Waiting for lock的会话,就需要查 performance_schema。

MySQL 5.7 以后提供了比较完整的数据字典,常用查询如下:

SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, r.trx_query AS waiting_query, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_query FROM performance_schema.data_lock_waits w JOIN performance_schema.data_locks l ON w.REQUEST_ENGINE_LOCK_ID = l.ENGINE_LOCK_ID JOIN information_schema.innodb_trx r ON r.trx_id = w.REQUESTING_TRX_ID JOIN information_schema.innodb_trx b ON b.trx_id = w.BLOCKING_TRX_ID;

这条 SQL 可以直接告诉我们:哪个事务在等,等的是哪个事务的锁,阻塞线程是哪个。拿到阻塞线程的trx_mysql_thread_id之后,如果确认是异常长事务,可以手动 kill 掉那个线程,快速恢复业务。

在 5.7 版本还可以直接用 sys 库的视图:

SELECT * FROM sys.innodb_lock_waits\G

这个视图已经把连接 ID、SQL 文本、锁等待时间都格式化好了,排查效率非常高。我在线上问题处置时,基本就是一条sys.innodb_lock_waits查出来,直接定位阻塞源,再决定 kill 还是等待。

常用的排查命令对应关系:

命令 / 视图作用产出
SHOW ENGINE INNODB STATUS查看最近一次死锁详情事务持有锁、等待锁的完整链路
SHOW PROCESSLIST查看当前所有会话状态是否有大量Waiting for lock会话
sys.innodb_lock_waits实时展示锁等待关系谁阻塞了谁、等待多久
performance_schema.data_locks查看当前所有锁记录每个事务持有哪些锁

6. 降低锁竞争的实操经验与方法论

6.1 事务设计层面的锁缩减

锁竞争最根本的解法是让事务持有的锁时间短、范围小。锁持有时长和事务时长强相关,一个事务从开始到提交之间所有加的锁都会一直占用。所以:

第一,事务体量要小。很多业务喜欢在一个事务里做很多次查询和更新,比如先查用户信息、再扣余额、再写订单、再更新优惠券状态,中途还夹杂几个远程调用。这种事务一旦并发上来,锁竞争指数级增加。我的经验是将远程调用、复杂计算全部挪到事务外面,事务里只保留必要的数据库读写。

第二,批量操作用小批次。比如一次更新 1 万条记录,换成每批 500 条提交,虽然总时间变长了,但每条记录被锁住的时间大幅缩短,其他事务的等待概率就下降了。这个对全表更新、批量修复数据尤其重要。

第三,避免大范围的无谓更新。给所有行设置同一个值,明明用一条 SQL 就能完成,但这条 SQL 可能锁住整张表的所有相关间隙。如果业务允许,可以按主键范围拆成多条语句,或者放到低峰期执行。

6.2 索引设计与 SQL 优化

锁竞争和索引的关系极其密切。同样一条UPDATE语句,走没走对索引,锁的范围可能相差几十倍。

经验法则:

  • 高频更新语句的 WHERE 条件必须命中索引,而且最好是唯一索引或主键。如果 WHERE 条件上没有索引,InnoDB 会退化成全表扫描,把所有扫描过的记录都锁住,等同于锁表。
  • 使用等值条件而不是范围条件,能缩小加锁范围。举个实际例子,UPDATE orders SET status = 2 WHERE create_time > '2025-01-01'这种语句,即便create_time有索引,锁的范围也覆盖了大量记录;如果业务上可以改成WHERE id IN (...)的等值条件组合,锁只落在指定 ID 上。
  • 用EXPLAIN检查执行计划,看是不是走了预期索引。我见过太多"以为走了索引,实际是 full scan"的情况,尤其在 OR 条件、函数包裹字段、隐式类型转换等场景下,索引会静默失效,锁范围瞬间扩大。

6.3 业务层的降锁策略

数据库层的优化是有极限的,真正高并发场景还要配合业务层的策略。

一种方式是乐观锁代替悲观锁。表里加一个版本号字段version,更新时带上版本条件:

UPDATE orders SET amount = 100, version = version + 1 WHERE id = 10 AND version = 1;

如果受影响行数为 0,说明数据已被别人改过,业务层做重试或报错。这种方式完全不依赖数据库行锁,并发能力高很多。它适合"冲突率低"的场景,比如更新自己的订单状态;不适合"多人抢同一资源"的热点场景。

另一种是异步化、队列化。热点账户的余额变更、秒杀库存扣减,这类操作如果全部并发更新同一行,不管怎么调数据库都会大量锁等待。现实的做法是把请求串行化:发到 MQ,由消费者单线程处理同一个 key 的更新,数据库层几乎不产生锁等待。本质上是用顺序换取并发。

还有一种是热点行拆分。对所有写压力集中的单行(比如爆款商品的库存字段),拆成多行子账户存储,通过哈希或随机因子分散不同请求的更新目标,最后汇总余额。这个方案改造量大,一般只在出现极端热点时才值得做。

6.4 监控与应急处理

锁问题很难提前 100% 发现,所以监控和应急手段是最后一道防线。

线上我比较依赖的检查逻辑是:

  • 开启慢查询日志,把执行超过 1 秒的语句打出来,定期分析有没有异常的锁等待语句;
  • 对show processlist中State字段包含Locked或Waiting for table metadata lock的会话做告警,及时处理长事务;
  • 定期巡检死锁日志,把所有死锁记录收集到日志系统,按事务 SQL 聚合,看哪些 SQL 组合频繁死锁。

应急处理的经验顺序是:先定位是谁阻塞的(建议用sys.innodb_lock_waits),如果是偶发的长事务,kill 掉恢复业务即可;如果是 SQL 本身锁范围太大,kill 之后还要分析执行计划、调整 SQL 或索引,否则很快会再次爆发。

最后再分享一个我自己的习惯:所有核心业务的更新语句,我都会刻意检查它走的是主键还是二级索引,以及锁的范围会不会覆盖到其他并发路径。MySQL 的锁不像很多人的直觉那样"只锁一行",它锁的是索引区间,一个不小心的范围条件,就能把整张表的关键路径卡死。对锁的理解越深,你就越知道怎么写出真正高并发的应用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询