news 2026/10/1 22:35:53

MySQL UPDATE 执行全过程:从加锁、日志到刷盘的一次完整旅行

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL UPDATE 执行全过程:从加锁、日志到刷盘的一次完整旅行

如果你在线上执行一条UPDATE,影响行数返回 1,事务提交成功。然后呢?这条 SQL 在 MySQL 内部到底干了多少件事?说实话,我做了几年后端,很长一段时间对 UPDATE 的理解都停留在“加锁、改数据、写 binlog”这种粗颗粒度上,直到有次排查一个线上慢更新,被同事问了一句“你知道现在这条 UPDATE 卡在哪个环节吗”,我才发现自己根本答不全。这篇文章就把一条 MySQL UPDATE 从客户端发起到最终落盘的完整流程拆开讲,涉及连接器、解析器、优化器、执行器、InnoDB 的行锁与 undo log、redo log 与 binlog 两阶段提交、后台刷脏等环节。如果你也在用 MySQL,不管是后端开发、运维还是 DBA,这篇都值得花时间读完,尤其是在遇到 UPDATE 慢、死锁、主从延迟时,能帮你快速定位问题。

1. 第一站不是执行,而是“过安检”:连接器与解析器做了哪些事

1.1 身份认证与权限的三次校验

客户端发出一条 UPDATE,第一个接触到的是 MySQL 的连接器。连接器做的事情很直接:管理连接、校验账号密码、维护会话状态。很多人觉得权限校验在这里做一次就够了,但实际不是。

MySQL 对权限的检查是分层、分时机的。第一次,建立连接时校验账号和密码是否合法;第二次,在预处理器阶段检查你有没有该表的 UPDATE 权限;第三次,在执行器真正调用 InnoDB 接口修改数据行时,还会再校验一次列级权限。这意味着什么?哪怕你用的是 root 账号连上了,也不代表任何 UPDATE 都能执行,权限会在后续阶段被反复确认。

这样做的好处是能尽早拦截非法操作,避免把 SQL 一路带到存储引擎层才发现没权限,白白浪费解析和优化成本。很多同学在“为什么我用普通账号执行 UPDATE 报错,但 SELECT 正常”这类问题上卡住,其实就是没搞懂 UPDATE 的权限校验不止发生在连接阶段。

1.2 解析器:把 SQL 文本变成语法树

过了连接器,SQL 文本进入解析器。这里会发生词法分析和语法分析两个动作。

词法分析负责把字符串拆成一个个“单词”,比如UPDATE、account、SET、balance、WHERE,每个词都有类型;语法分析则根据 MySQL 的语法规则,把这些词组装成一棵语法树。如果 SQL 本身写错了,比如UPDAET account SET这种拼写错误,会在这一步直接报语法错误,根本到不了后面的优化器。

值得注意的一个区分是:Unknown column 'xxx' in 'where clause'这类错误,其实不是解析器报的,而是预处理器报的。解析器只负责“结构对不对”,不负责“字段存不存在”。很多人在排查报错时把这两层混在一起,定位时就容易走弯路。

1.3 预处理器:字段与表的存在性检查

语法树生成后,预处理器开始干活。它会把语法树里的表名、字段名解析成 MySQL 内部的表 ID、字段 ID,同时检查这些对象是否存在,并完成表级权限的校验。

这一步也决定了为什么 MySQL 在 8.0 之前还有一个“查询缓存”机制会对 UPDATE 产生额外影响:在 5.7 及更早版本,UPDATE 会使得涉及表的所有查询缓存失效。也就是说,一条 UPDATE 不只是改数据,还会“顺便”清掉一堆 SELECT 的缓存。8.0 直接把查询缓存移除了,一方面是因为维护代价太高,另一方面也是因为这种缓存失效机制在高并发更新场景下几乎起不到加速作用。如果你还在用 5.7,要意识到 UPDATE 频繁的表,查询缓存命中率基本是零,不如直接把 query_cache_type 关掉。

2. 优化器拍板:UPDATE 的索引选择不是“拍脑袋”

2.1 优化器到底在优化什么

很多人以为 UPDATE 是直接“按条件找到行就改”,没有执行计划一说。这是误解。UPDATE 和 SELECT 一样,也会经过优化器,并且优化器的决策直接决定这次更新会扫描多少行、加多少锁。

优化器要解决的核心问题,是找到一条“代价最小”的访问路径。代价模型里主要考虑两类成本:IO 成本和 CPU 成本。比如 WHERE 条件是主键等值,那最优路径显然是主键索引直接定位;WHERE 条件是普通二级索引,就要评估走索引回表的代价,还是干脆全表扫描更快;WHERE 条件涉及多表时,还要决定连接顺序。

有一个关键差异点:普通 SELECT 的优化只影响“读”的代价,而 UPDATE 的访问路径还直接影响“锁”的范围。扫描的行越多,加的锁越多,阻塞其他事务的概率越大。所以 UPDATE 的优化比 SELECT 多一层并发维度的考量。

2.2 用 EXPLAIN 直接看 UPDATE 的执行计划

MySQL 从 5.6 开始就支持EXPLAIN UPDATE,你可以直接在一条 UPDATE 前面加 EXPLAIN,看优化器打算怎么干。举个例子:

EXPLAIN UPDATE account SET balance = balance - 100 WHERE id = 12580;

如果id是主键,执行计划里的 type 一般是const,表示主键等值查找,扫描行数为 1,代价极低,锁也只加在这一行上。

但如果把条件换成二级索引:

EXPLAIN UPDATE account SET balance = balance - 100 WHERE user_level = 3;

假设user_level上有普通索引,执行计划可能是ref,优化器会根据索引区分度估算要访问多少行。如果区分度不好,优化器可能直接选择ALL,也就是全表扫描。在全表扫描的情况下,InnoDB 会扫描聚簇索引的每一个数据页,对经过的每一条记录做加锁判断——这就是 UPDATE 慢、锁冲突高的常见根源。

2.3 统计信息滞后导致选错索引的真实案例

有次我处理过一个线上问题:一条 UPDATE 平时执行只要几十毫秒,某一天突然变成几秒钟,把连接池都拖满了。EXPLAIN 一看,原来该走的索引没走,优化器选了另一个区分度很差的索引,扫描行数从几百行暴涨到几十万行。

原因是 MySQL 优化器依赖统计信息来估算各索引的选择率。如果表数据量发生大幅变化,比如批量导入了大量数据,但统计信息还没有被更新,优化器手里就是一份过期的“地图”。解决方法是执行ANALYZE TABLE,重新收集统计信息。这个操作很轻量,但在线上却经常被人忽略。

从此我养成了一个习惯:任何 UPDATE 变慢的排查,第一步永远是用 EXPLAIN 看执行计划,确认扫描行数和 type 类型,再谈其他。执行计划不对,后面调什么参数都是白费。

3. 当前读的代价:InnoDB 如何给 UPDATE 加锁

3.1 为什么 UPDATE 必须“读最新版本”

InnoDB 的并发控制基于 MVCC,但 MVCC 有个非常重要的区分:快照读和当前读。

普通SELECT ...默认是快照读,读取的是事务开始时的快照版本,不加锁。而 UPDATE 属于当前读,必须读取记录的最新版本,并且对读取到的记录加锁。原因很朴素:你要修改一条记录,就得基于最新值来改,如果基于旧快照改,那就会覆盖掉别人已经提交的修改。

当前读和快照读的差异,是很多“为什么我在事务里明明 SELECT 看不到这条数据,但 UPDATE 却成功了”类困惑的根源。你 SELECT 用的是快照,UPDATE 用的是当前读,看到的版本本来就不一样。

3.2 记录锁、间隙锁与 next-key lock:一次 UPDATE 会锁住什么

InnoDB 加锁的最小单位是记录,但在可重复读(RR)隔离级别下,为了防止幻读,InnoDB 还对索引记录之间的间隙加锁。所以一次 UPDATE 实际会涉及三种锁:

  • 记录锁(Record Lock):锁定单个索引记录。
  • 间隙锁(Gap Lock):锁定记录之间的“空隙”,不允许其他事务在这个间隙里插入新记录。
  • 临键锁(Next-Key Lock):记录锁和间隙锁的组合,锁定一个区间。

举个例子,UPDATE account SET balance = balance - 100 WHERE id > 100 AND id < 200。在主键索引上,InnoDB 会对(100, 200)这个范围内的所有索引记录加记录锁,同时对边界两侧的间隙加间隙锁。这意味着,即使你的 UPDATE 只匹配到 20 行,它也可能锁住了 100 到 200 这个区间的“插入权限”。

很多人踩过的坑就在这里:明明更新的行数很少,但其他事务想往同一个区间插入数据时却一直阻塞,最后锁等待超时。这不是 MySQL 的 bug,而是 RR 下的临键锁机制在起作用。

3.3 RR 与 RC 下 UPDATE 锁的差异

锁的行为和隔离级别强相关。RC(读已提交)和 RR(可重复读)下,UPDATE 加锁的逻辑有明显区别:

对比维度RC(读已提交)RR(可重复读)
记录锁只锁匹配到的记录扫描过程中遇到的记录都可能加锁
间隙锁不加加,防止幻读
锁释放时机不匹配条件可及时释放事务结束才统一释放
典型后果锁范围小,并发高锁范围大,容易出现锁等待

另外一个容易被忽视的点:RC 下,UPDATE 在扫描过程中如果遇到某行已经被其他事务锁定,InnoDB 会使用“半一致性读”,直接读取这行的最新版本来判断它是否满足 WHERE 条件。如果该行不满足条件,就直接跳过,不用等待锁。这也是 RC 下并发 UPDATE 相对不容易卡死的原因之一。

而 RR 下没有这种优化机制,所有扫描过的记录都会被加锁,锁等待的概率自然更高。如果你业务对幻读并不敏感,把隔离级别从 RR 调成 RC,对 UPDATE 并发性能的提升往往立竿见影。这也是很多互联网公司默认使用 RC 的原因。

3.4 锁等待和死锁的排查方法

遇到 UPDATE 一直卡住,先别急着猜,用命令看锁现场。

MySQL 5.7 之后可以查询performance_schema.data_lock_waits,或者用 sys 库里的sys.innodb_lock_waits视图,能直接看到谁在等哪把锁、锁被谁持有。最简单粗暴的办法是执行:

SHOW ENGINE INNODB STATUS;

这个命令会输出当前 InnoDB 的事务和锁信息,包括死锁检测记录。如果存在死锁,InnoDB 会自动回滚其中一个事务,并在输出里留下两个事务互相持有锁的“案发现场”。

死锁最常见的场景是两个事务以不同顺序更新多张表。比如事务 A 先 UPDATE 表 t1 再 UPDATE 表 t2,事务 B 先 UPDATE 表 t2 再 UPDATE 表 t1,两边各持一把锁又互相等对方释放,就死锁了。解决思路通常是把多表更新的顺序统一,或者尽量缩小事务范围,让锁持有时间变短。

4. 内存中的手术台:Buffer Pool、undo log 与 change buffer

4.1 先把数据页请进 Buffer Pool

加锁只是第一步,真正开始修改数据前,InnoDB 需要先把目标行所在的数据页加载到内存。InnoDB 的读写单位是页,默认 16KB,也就是说你哪怕只改一个字段,加载到内存的最小单位也是一个完整的数据页。

如果目标页已经在 Buffer Pool 里,直接使用;如果不在,就需要从磁盘读入。这个过程是随机 IO,通常也是 UPDATE 耗时的来源之一。Buffer Pool 越大,热点数据页缓存的概率越高,随机 IO 越少。

这里有一个内存层面的细节:修改一个数据页之前,InnoDB 还需要对页加锁(latch),防止另一个线程同时修改同一个页。这个锁和前面说的行锁不是一回事,行锁是事务层面的,页锁是内存操作层面的。如果你在SHOW ENGINE INNODB STATUS里看到RW-latches相关等待,多半是页级别的竞争,而不一定是业务锁冲突。

4.2 更新前先记旧账:undo log 的生成与 MVCC

真正修改数据行之前,InnoDB 必须先把“旧值”记录下来,这就是 undo log。undo log 存在系统表空间的 undo 页里,作用有两个:事务回滚和 MVCC 快照读。

以 UPDATE 为例,InnoDB 在聚簇索引上会先给旧记录打上删除标记,再插入一条新记录。这条新记录里有两个隐藏字段:trx_id记录修改它的事务 ID,roll_ptr指向 undo 链上的一条旧版本记录。如果事务回滚,InnoDB 沿着roll_ptr找到旧记录恢复即可;如果其他事务要做快照读,也是通过这条 undo 链找到自己可见的版本。

很多人对 UPDATE 的理解是“原地覆盖”,但 InnoDB 的实现更接近“标记删除 + 插入新版本”。这也是为什么频繁 UPDATE 的表,undo log 会快速增长,甚至出现“undo 膨胀”问题。长事务尤其危险:一个事务一直不提交,它创建的旧版本就不能被 purge 清理,undo 文件会越来越大。

4.3 change buffer:二级索引更新时的优化路径

UPDATE 要修改的数据,如果涉及二级索引,InnoDB 其实还有一个优化手段:change buffer。

当要更新的二级索引页不在 Buffer Pool 中时,InnoDB 可以不立即把索引页读入内存,而是先把变更缓存到 change buffer 里,等这个索引页将来被读取时,再把缓存的操作合并进去。这样一来,一次 UPDATE 就省掉了一次随机读取二级索引页的 IO。

但要注意,change buffer 只对“非唯一”二级索引有效。唯一索引必须读取索引页来检查唯一性冲突,所以没法走这个优化。如果你的表上有大量二级索引,且更新很频繁,change buffer 能明显降低写放大;反过来,如果二级索引基本都是唯一索引,那 change buffer 基本没什么用。

还有个容易踩的坑:change buffer 本身会占用 Buffer Pool 的一部分内存,默认最大 25%。如果机器内存本来就紧张,频繁的索引更新又不来 merge,change buffer 本身也可能成为磁盘 IO 的来源。

4.4 页分裂与碎片:频繁 UPDATE 的存储层代价

UPDATE 如果改变了行的长度,比如把一个VARCHAR字段从短值改成很长的新值,导致行大小膨胀,InnoDB 可能会触发页分裂。一个 16KB 的页装不下了,就要申请新页、调整记录位置。这不仅消耗 IO,还会让表产生碎片。

碎片增多后,全表扫描的代价会变高,因为同样的数据量占用了更多的页。如果你发现一张表 UPDATE 和 DELETE 非常频繁,但业务又不关心物理存储布局,可以定期OPTIMIZE TABLE来整理碎片。不过这个操作会锁表,必须放在维护窗口执行,不能线上裸跑。

5. 崩溃安全的基石:redo log 与 binlog 的两次握手

5.1 WAL:先写日志,再改数据

InnoDB 的修改流程里,内存中的脏页并不会立刻写回磁盘,而是先写 redo log。这就是 WAL(Write-Ahead Logging)机制:先写顺序日志,再改数据文件。

redo log 是 InnoDB 引擎层的物理逻辑日志,记录的是“某个数据页的某个偏移量发生了什么变更”。它是追加写入,顺序 IO,比随机写数据页快几个数量级。这条日志的存在,保证了即使还没刷盘,事务提交后数据也不会丢——崩溃后可以靠 redo log 重放来恢复。

这里有个常被误解的点:redo log 不是等事务提交才写的,而是边改数据边往 redo log buffer 里写。事务提交时,根据参数决定要不要把 buffer 里的日志刷到磁盘。

5.2 两阶段提交的三个动作

redo log 和 binlog 分别属于两层:redo 是 InnoDB 引擎层的,binlog 是 MySQL Server 层的。两个日志各自独立记录,如果不做协调,崩溃后可能出现“一个说改了,一个没说改”的不一致。

MySQL 用两阶段提交保证两个日志的一致性。一条 UPDATE 提交时,顺序是这样的:

  1. InnoDB 先把本次修改对应的 redo log 写入 redo buffer,状态标为 prepare。
  2. Server 层写 binlog,并调用sync_binlog把 binlog 刷到磁盘。
  3. binlog 落盘成功后,InnoDB 再把 redo log 的状态改为 commit,并触发 redo 的刷盘。

也就是说,redo 的 prepare 表示“我这边准备好了”,binlog 的写入是“两个日志的仲裁者”,redo 的 commit 表示“这次事务正式成立”。

5.3 崩溃恢复时如何裁决:提交还是回滚

两阶段提交的价值在崩溃恢复时体现得淋漓尽致。如果数据库在某个时刻突然宕机,重启后 InnoDB 会扫描 redo log,发现处于 prepare 状态的事务,然后去 binlog 里查对应的事务 XID:

  • redo log 里已经有 commit 标记:事务已经完整提交,直接提交。
  • redo 是 prepare,且 binlog 里能找到对应的 XID 事件:事务其实已经在 Server 层记录完整,恢复时继续提交。
  • redo 是 prepare,但 binlog 里找不到对应 XID:说明 binlog 还没写完,两阶段提交不完整,恢复时回滚这个事务。

这个机制保证了主库崩溃后,从库从 binlog 恢复出来的数据和主库 redo log 恢复出来的数据是一致的,不会出现主库有这条数据、从库没有,或者反过来。理解了这一步,你就理解了为什么 binlog 不能丢、不能乱序。

5.4 刷盘参数权衡与组提交

两阶段提交里涉及的刷盘动作,是由参数控制的:

参数取值行为数据安全性能
innodb_flush_log_at_trx_commit1每次提交刷 redo 到磁盘最安全最慢
innodb_flush_log_at_trx_commit2提交时写入 OS 缓存,每秒刷盘可能丢 1 秒数据较快
innodb_flush_log_at_trx_commit0每秒刷盘,提交时不主动刷可能丢更多最快
sync_binlog1每次提交刷 binlog 到磁盘最安全最慢
sync_binlogN每 N 次提交刷一次 binlog可能丢 N 个事务较快

生产环境默认推荐innodb_flush_log_at_trx_commit=1和sync_binlog=1,这个组合下,一个事务提交成功,意味着 redo 和 binlog 都已经真正落到磁盘。性能上,MySQL 5.7 之后通过组提交把多个事务的刷盘动作合并,能抵消一部分 fsync 开销。但如果你对性能要求极高且能容忍少量数据丢失,也可以放宽这两个参数,这就是一个明确的“性能换安全”的取舍,没有中间地带。

6. 提交之后的故事:脏页刷盘与数据最终落盘

6.1 “更新成功”与实际落盘是两回事

回到开头那个问题:一条 UPDATE 返回“影响行数 1”,事务提交成功,是不是意味着数据已经写进磁盘了?

不一定。默认配置下,事务提交成功只表示 redo log 和 binlog 已经刷盘,但真正的数据页可能还是 Buffer Pool 里一个“脏页”,还没有写回到磁盘上的数据文件。如果此时数据库崩溃,靠 redo log 重放,数据照样能恢复;但如果整个磁盘设备损坏、redo log 也一起丢了,那最近一段时间的数据就真没了。

所以,日志先行并不等于数据落盘。日志是“记账本”,数据页是“账本余额”,账本先记好,余额可以慢慢改。

6.2 后台线程如何把脏页刷出去

脏页不会一直躺在内存里,InnoDB 有专门的后台线程负责刷盘,也就是 page cleaner。它会根据脏页比例、当前 IO 能力、以及 redo log 的写入速度,动态决定刷盘节奏。

这里涉及一个经典参数组合:innodb_io_capacity决定刷盘 IO 的上限,innodb_max_dirty_pages_pct决定脏页比例上限。如果 Buffer Pool 设置得很大,而innodb_io_capacity又给得很小,就可能出现脏页积压。最典型的症状是:某段时间突然出现大量 UPDATE 变慢,因为数据库开始“主动刷脏”以降低脏页比例,抢走了业务 IO。

MySQL 8.0 里有自适应刷盘机制,会根据 redo log 生成速率调整刷盘强度,比老版本聪明很多。但即便如此,io_capacity的设置仍要匹配实际机器硬件能力。SSD 和机械盘能承受的刷盘压力差别很大,参数不能照抄。

6.3 半页损坏防线:doublewrite buffer

刷盘是后台线程把 16KB 的数据页写到磁盘文件。磁盘扇区一般是 4KB,意味着一个页要分 4 次写。如果刷盘过程中发生断电,可能出现“前一半是新数据、后一半是旧数据”的半页损坏现象,这个页既不是更新前的完整模样,也不是更新后的完整模样,redo log 也救不回来,因为 redo log 是基于页的重放,不能修复一个本身结构损坏的页。

为了对付这种极端情况,InnoDB 引入了 doublewrite buffer。在把脏页写到真实位置之前,先把页完整地 copy 到 doublewrite buffer 区域,再统一写回磁盘。如果系统在刷脏过程中崩溃,重启后 InnoDB 会检查数据页是否损坏,损坏的话用 doublewrite 里的副本恢复。

这也是为什么你会在数据目录里看到命名为#ib_16384_0.dblwr之类的文件。它看起来占空间,但它是最后一层防线。

6.4 checkpoint 推进与运维手记

最后一个是 checkpoint 的概念。redo log 是环形复用的,而复用之前,InnoDB 必须保证这个日志点之前的所有脏页都已经刷到了磁盘。这个“可以安全覆盖日志”的位置,就是 checkpoint。

checkpoint 不断向前推进,意味着越来越多的数据已经落盘、崩溃恢复需要重放的日志越来越少。如果系统负载很高、刷脏跟不上,checkpoint 就会停滞,redo log 空间被耗尽,整个数据库会被迫停下来强制刷盘,这就是最典型的“提交正常但数据库越来越卡”的场景之一。监控上建议关注两件事:脏页比例和 redo log 空间利用率。前者过高说明刷脏能力不足,后者接近 100% 说明 checkpoint 已经拉响了警报。

就我的实操经验来说,遇到 UPDATE 慢,建议先别急着改 SQL。按流程一层层排:先看锁等待,再看扫描行数和执行计划,然后看磁盘 IO 和刷盘参数。把这条链路理顺了,很多所谓玄学问题,最后都不过是在某个环节上配置失配而已。

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

C#实战:UE4游戏内存读取与TheIsle恐龙岛数据插件开发

最近有个朋友问我&#xff1a;能不能用 C# 做一个 TheIsle 恐龙岛的本地数据插件&#xff0c;把游戏里的恐龙名字、坐标、距离实时读出来。听完需求我就知道&#xff0c;这其实是个很典型的“读取游戏基址 UE4 对象模型分析”实战题。TheIsle 看着是个恐龙生存游戏&#xff0c…

作者头像 李华
网站建设 2026/10/1 22:33:00

Java+JSP+MySQL电子健康档案系统毕设源码拆解与实战

简介&#xff1a;这是一套面向高校计算机相关专业毕业设计场景的电子健康档案系统完整源码&#xff0c;采用JavaJSPMySQL技术栈实现&#xff0c;适合正在准备毕设的学生、需要Java Web实战案例的初学者&#xff0c;以及希望参考医疗信息化系统架构的开发者。压缩包共760个文件&…

作者头像 李华
网站建设 2026/10/1 22:32:42

海明码与海明距离:数据链路层差错控制的核心原理与计算

网络传输没有什么是绝对可靠的。信号在介质里跑一圈&#xff0c;可能被电磁干扰、被噪声顶了一下&#xff0c;一个0就变成了1。数据链路层为了解决这种问题&#xff0c;引入了差错控制。而在这个话题里&#xff0c;最绕不开的两个词就是海明距离和海明码——一个告诉你编码的抗…

作者头像 李华
网站建设 2026/10/1 22:31:20

C++中关键字constexpr的实现示例

constexpr 是 C11 引入并在后续标准&#xff08;C14/C17/C20&#xff09;中持续增强的关键字&#xff0c;其核心作用是在编译期计算常量或表达式&#xff0c;既保证了编译期的安全性检查&#xff0c;又能消除运行期计算的开销&#xff0c;是实现“零成本抽象”和编译期元编程的…

作者头像 李华
网站建设 2026/10/1 22:30:21

后缀表达式求值:栈原理、中缀转逆波兰表达式完整解析

第一次在洛谷刷到P1449 后缀表达式时&#xff0c;我盯着这个名词愣了好一会儿。平时写惯了3*(5-2)7这种中缀式子&#xff0c;突然冒出一个把运算符全丢在后面的3.5.2.-*7.&#xff0c;第一反应是&#xff1a;这玩意儿真的是给人读的吗&#xff1f;不过也正是这道题&#xff0c;…

作者头像 李华
网站建设 2026/10/1 22:30:11

grep组合拳:高效排查大日志文件的实用技巧

上周三下午&#xff0c;我正在工位上改脚本&#xff0c;隔壁同事探过头来&#xff0c;一脸无奈&#xff1a;“哥&#xff0c;这个日志文件十几个 G&#xff0c;我用 VS Code 一打开就卡死&#xff0c;等半天只看到转圈。我把报错那几行复制给你行不行&#xff1f;”我说你先别急…

作者头像 李华