MySQL 事务隔离:从脏读、幻读到 MVCC 与 Next-Key Lock
我是安徽最忧郁程序员 无隅事务隔离最容易学成一张需要死记的表四种级别、三种异常再加上 MVCC 和各种锁。表格背下来以后一旦问题变成“InnoDB 的 RR 到底会不会幻读”结论就开始互相冲突。问题的根源是那张表只描述了隔离级别的目标没有解释 InnoDB 如何实现这些目标。理解 InnoDB 事务隔离的关键是先判断一条语句读取哪个数据版本再判断它是否加锁、锁住哪段索引范围。沿着这条主线四种隔离级别、MVCC 和 Next-Key Lock 才能真正连在一起。1. 为什么事务并发会产生读取异常假设事务 A 正在查询数据事务 B 同时修改或插入数据。如果数据库不约束两个事务之间的数据可见性事务 A 的读取结果就可能受到事务 B 执行进度的影响。常见的并发读取异常有三种。脏读Dirty Read是事务 A 读到了事务 B 尚未提交的数据。事务 B 一旦回滚事务 A 读到的值就从来没有真正生效过。不可重复读Non-Repeatable Read是事务 A 在同一事务中两次读取同一行事务 B 在两次读取之间修改该行并提交导致事务 A 得到了两个不同的值。它关注的是同一行的值发生变化。幻读Phantom Read是事务 A 两次执行相同的范围查询事务 B 在中间插入或删除了满足条件的行导致第二次查询的结果集发生增减。它关注的是满足条件的行集合发生变化。可以用一句话区分后两者不可重复读原来的行还在但行里的值变了。幻读相同条件下结果集中多行或少行了。这些异常不是三个孤立概念而是数据库设置不同隔离级别时需要逐步限制的并发行为。2. 四种隔离级别本质上是在限制事务能看见什么事务隔离级别控制的核心是一个事务能够看见其他事务执行到哪个阶段的数据。隔离级别越高事务之间的可见性约束越强但锁等待和并发成本通常也会增加。隔离级别读取语义脏读不可重复读幻读READ UNCOMMITTED可能读取其他事务尚未提交的数据可能可能可能READ COMMITTED每次语句读取当时已经提交的数据避免可能可能REPEATABLE READ同一事务中的一致性读通常复用同一快照避免避免标准层面仍可能SERIALIZABLE使用更严格的锁定规则获得可串行化效果避免避免避免READ UNCOMMITTED 的隔离性最低。它允许普通读取接触尚未提交的版本因此可能出现脏读查询结果也不保证可以重复。READ COMMITTED简称 RC。每次一致性读都会建立新的快照所以只能读取已经提交的数据但同一事务的下一次查询可能看见其他事务刚提交的修改。REPEATABLE READ简称 RR。同一事务中的一致性读通常基于第一次一致性读建立的快照后续普通SELECT因而能够看到一致的数据状态。SERIALIZABLE 的隔离约束最强。以 InnoDB 为例在关闭自动提交时普通SELECT会被隐式转换为SELECT ... FOR SHARE。它的目标是让并发执行的结果等价于某种串行顺序不等于数据库简单地让所有事务全局一个接一个运行。这里必须分清两个层次SQL 标准说明每种隔离级别需要避免哪些并发现象。InnoDB 再通过 MVCC、记录锁、间隙锁等机制实现这些隔离语义。因此通用表格可以帮助建立第一层认识却不能直接回答 InnoDB 在某条具体 SQL 上会不会出现幻读。3. 为什么 InnoDB 默认选择 REPEATABLE READMySQL 8.4 官方文档明确说明InnoDB 支持四种事务隔离级别默认使用 REPEATABLE READ。可以通过下面的语句查看当前会话的隔离级别SELECTtransaction_isolation;也可以只修改当前会话避免影响其他连接SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;RR 能成为 InnoDB 的默认级别关键不只是它在隔离级别表中处于一个较高位置而是 InnoDB 为两种读取方式分别准备了机制普通SELECT属于一致性非锁定读通常使用 MVCC 读取快照。SELECT ... FOR UPDATE、SELECT ... FOR SHARE等锁定读以及UPDATE、DELETE需要读取并锁定较新的数据库状态。对于前者旧版本让读取不必阻塞写入对于后者记录锁、Gap Lock 和 Next-Key Lock 又能保护将要修改的数据与索引范围。RR 的实际能力来自“MVCC 锁”的组合而不是某一个机制单独完成全部工作。4. MVCC不加读锁也能读到一致的数据版本MVCC 的全称是 Multi-Version Concurrency Control即多版本并发控制。它解决的核心问题是当一行数据正在被修改时普通查询怎样在不等待写事务结束的情况下仍然读到一个一致的结果答案不是复制多张完整的数据表而是利用记录中的事务信息和 undo log按需重建当前查询能够看见的历史版本。4.1 MVCC 的本质假设某行数据先后从 100 修改为 200又从 200 修改为 300。聚簇索引记录中保存的是较新的状态而构造旧版本所需的信息保存在 undo log 中。当一个较早建立的快照需要读取这行数据时InnoDB 会从当前记录出发沿回滚指针向历史版本追溯直到找到对当前 Read View 可见的版本。所以MVCC 中的“多版本”更准确地说是当前记录加上 undo log 中可用于重建历史状态的信息。普通一致性读不必给读取到的行加共享锁。其他事务仍然可以更新这些行而当前查询可以通过历史版本继续完成读取这就是常说的“读写不冲突”。4.2 MVCC 的三个关键组成第一部分是 InnoDB 记录中的内部事务信息。DB_TRX_ID记录最后一次插入或更新该行的事务标识。DB_ROLL_PTR指向 undo log 中相关记录的回滚指针。DB_ROW_ID当表中没有主键也没有合适的非空唯一索引作为聚簇索引时InnoDB 才会生成并使用隐藏行标识。因此把DB_ROW_ID说成每一行都必然使用的 MVCC 字段并不严谨。真正参与版本追溯的核心是事务标识、回滚指针和 undo log。第二部分是undo log。事务修改聚簇索引记录时会写入撤销信息。它既服务于事务回滚也能在一致性读需要旧数据时重建历史版本。MySQL 关于 undo log 的说明也明确指出一致性读需要原始数据时可以从 undo log 记录中获取。第三部分是Read View。它可以理解为一次一致性读对事务可见性的判断依据记录了创建视图时活跃事务的大致边界。InnoDB 检查版本上的事务标识判断该版本是在快照之前已经提交、由当前事务产生还是当时仍不可见。Read View 自身不会复制数据也不会修改版本链。它只负责回答一个问题这个版本对当前快照是否可见4.3 RC 与 RR 的关键差异RC 和 RR 都会使用一致性读与历史版本但二者创建快照的时机不同。在 READ COMMITTED 下每次一致性读都会建立新的快照。事务 A 第一次查询后如果事务 B 修改并提交事务 A 的第二次普通查询就可能看见新值。在 REPEATABLE READ 下同一事务中的一致性读通常复用第一次一致性读建立的快照。需要注意快照默认不是执行START TRANSACTION的瞬间就一定创建而是在第一次一致性读时建立START TRANSACTION WITH CONSISTENT SNAPSHOT是一个需要单独讨论的显式方式。这也说明“MVCC 只在 RC 和 RR 下工作”是一种便于入门但过于绝对的说法。更严谨的表述是RC 与 RR 是讨论 InnoDB 一致性快照语义时最核心的两个隔离级别它们最主要的差异是快照更新频率。5. 幻读为什么不能只用 MVCC 解释幻读涉及结果集中的行是否会增加或减少。分析它之前必须先确定 SQL 属于快照读还是当前读。5.1 快照读普通 SELECT在 RC 和 RR 下不带锁定子句的普通SELECT通常属于一致性非锁定读也就是常说的快照读。在 RR 中连续的普通SELECT通常复用同一个 Read View。即使其他事务在中间插入并提交了满足条件的新行这个新版本对旧快照仍不可见事务 A 的第二次快照读也就不会突然多出一行。这种场景依靠的是 MVCC。它没有阻止事务 B 插入而是让事务 A 继续读取原来的数据快照。5.2 当前读读取并锁定最新状态如果查询结果接下来要参与修改旧快照通常不够用。例如系统查询一批待处理任务后准备更新它们就必须避免其他事务同时抢占相同任务。常见的锁定读包括SELECT...FORUPDATE;SELECT...FORSHARE;UPDATE和DELETE在定位并修改记录时同样需要读取并锁定较新的状态。这类语句在中文资料中经常被统称为当前读。当前读不能只依靠 MVCC因为它不仅要“看见什么”还要保护接下来将要读取或修改的对象。对于范围条件只锁住现有记录并不够其他事务仍可能从记录之间的空隙插入新行。5.3 Next-Key Lock 如何阻止幻影行插入InnoDB 的索引锁可以从三个概念理解Record Lock锁住一条已有的索引记录。Gap Lock锁住两条索引记录之间的间隙主要用于阻止插入。Next-Key Lock前开后闭的索引区间可以理解为“记录前的间隙 该索引记录”。图中的(10, 20]表示10 和 20 之间的间隙被保护同时索引记录 20 也被锁定。事务 B 尝试插入索引值 15 时会因为插入意向与间隙上的锁冲突而等待直到事务 A 提交或回滚。但不能把它简化成“只要是当前读就一定把条件范围全部加上 Next-Key Lock”。MySQL 官方隔离级别文档给出了更具体的边界使用唯一索引和唯一等值条件找到一条记录时InnoDB 通常只锁索引记录不锁前面的间隙。使用范围条件或非唯一条件时InnoDB 通常对扫描到的索引范围使用 Gap Lock 或 Next-Key Lock。在 RC 下间隙锁大部分被禁用主要保留给外键约束检查和重复键检查。锁的对象不是 SQL 文本里的抽象WHERE条件而是执行过程中实际扫描到的索引记录和索引间隙。索引设计和执行计划会直接影响加锁范围缺少合适索引时扫描与锁定范围都可能显著扩大。6. RR 到底有没有完全解决幻读这个问题不能脱离数据库实现和读取方式只回答“解决了”或“没有解决”。6.1 先给结论从 SQL 标准的一般定义看REPEATABLE READ 不承诺避免所有幻读SERIALIZABLE 才提供最强的可串行化保证。但在 InnoDB 的常见使用场景中连续快照读依靠固定 Read View通常不会看见其他事务后来提交的幻影行。正确的范围锁定读依靠 Gap Lock 或 Next-Key Lock通常能够阻止其他事务向受保护范围插入新行。因此更准确的结论是InnoDB 的 RR 通过 MVCC 和索引范围锁避免了大量典型幻读但它不是一句脱离 SQL 类型、索引和操作顺序的无条件保证。6.2 为什么“先快照读再 UPDATE”会产生理解冲突考虑下面的执行顺序事务 A 使用普通SELECT读取旧快照。事务 B 插入一条满足条件的新行并提交。事务 A 执行SELECT ... FOR UPDATE或UPDATE。第三步不能继续只看旧快照因为它需要基于较新的数据库状态完成锁定或修改。于是同一个事务中可能同时出现两幅不同的数据图景普通SELECT看到旧快照锁定语句看到较新的状态。很多面试资料把这种现象称为 RR 下的“特殊幻读”。如果严格按照“同一事务用相同查询执行两次”的定义它又不是两个完全相同的读取操作因为第二次已经从快照读切换成了当前读。MySQL 8.4 官方文档直接提醒不建议在同一个 RR 事务中混用非锁定SELECT与锁定语句。前者展示 Read View 中的旧状态后者使用较新的状态进行锁定两种表状态放在一起往往难以理解。所以这个案例真正要记住的不是“RR 突然失效”而是快照读与当前读服务于不同目标混用它们时不能假设所有语句都观察同一份数据库状态。6.3 工程上的正确选择如果业务只需要在一个事务中完成一致性查询可以使用 RR 下的普通快照读让 MVCC 提供稳定视图。如果先查询、后更新而且查询结果决定后续写操作应当从一开始就评估SELECT ... FOR UPDATE或SELECT ... FOR SHARE并为查询条件建立合理索引。这样锁定范围和业务意图才能保持一致。如果业务必须获得严格的可串行化语义可以评估 SERIALIZABLE但要接受更高的锁等待、冲突和重试成本。隔离级别不是越高越好而是要与一致性要求和并发压力匹配。7. 用两个会话验证 RC、RR 与当前读的差异下面使用一张简单的account表和一张带范围索引的orders表完成三个实验。建议准备两个 MySQL 客户端连接分别作为会话 A 和会话 B。先执行一次初始化DROPTABLEIFEXISTSaccount;CREATETABLEaccount(idINTPRIMARYKEY,balanceINTNOTNULL)ENGINEInnoDB;INSERTINTOaccountVALUES(1,100);DROPTABLEIFEXISTSorders;CREATETABLEorders(idINTPRIMARYKEY,amountINTNOTNULL,INDEXidx_amount(amount))ENGINEInnoDB;INSERTINTOordersVALUES(1,100),(2,200);实验一RC 与 RR 的快照差异会话 A 先使用 RCSETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;STARTTRANSACTION;SELECTbalanceFROMaccountWHEREid1;-- 100会话 B 修改并提交UPDATEaccountSETbalance200WHEREid1;COMMIT;会话 A 再次执行相同查询SELECTbalanceFROMaccountWHEREid1;-- 200COMMIT;RC 的第二次一致性读建立了新快照因此能够看到事务 B 已经提交的 200。把测试数据恢复为 100再将会话 A 的隔离级别换成 RR重复相同顺序SETSESSIONTRANSACTIONISOLATIONLEVELREPEATABLEREAD;STARTTRANSACTION;SELECTbalanceFROMaccountWHEREid1;-- 100-- 会话 B 将 balance 更新为 200 并提交后SELECTbalanceFROMaccountWHEREid1;-- 仍然是 100COMMIT;RR 的第二次普通查询复用了第一次一致性读建立的快照所以仍然得到 100。实验二RR 下的范围锁定读会话 A 对金额范围执行锁定读SETSESSIONTRANSACTIONISOLATIONLEVELREPEATABLEREAD;STARTTRANSACTION;SELECT*FROMordersWHEREamountBETWEEN100AND200FORUPDATE;会话 B 尝试插入落在该索引范围内的新值INSERTINTOorders(id,amount)VALUES(3,150);在典型的 RR 范围锁定场景中会话 B 会等待因为会话 A 已经对扫描到的索引范围及相关间隙加锁。会话 A 执行COMMIT后会话 B 才能继续。如果实际结果与预期不同应先检查当前隔离级别、表引擎、查询使用的索引和执行计划而不是只看WHERE条件的字面范围。实验三混用快照读与当前读先确保orders表中不存在amount 150的记录。会话 A 建立 RR 快照SETSESSIONTRANSACTIONISOLATIONLEVELREPEATABLEREAD;STARTTRANSACTION;SELECT*FROMordersWHEREamount150;-- 空结果会话 B 插入并提交INSERTINTOorders(id,amount)VALUES(3,150);COMMIT;会话 A 再执行一次普通SELECT仍然读取旧快照SELECT*FROMordersWHEREamount150;-- 仍为空随后改用锁定读SELECT*FROMordersWHEREamount150FORUPDATE;-- 可以读取并锁定新行COMMIT;两次查询结果不同不是因为 Read View 在事务中途被偷偷替换而是因为后一次语句已经切换到锁定读语义需要读取并锁定较新的状态。8. 总结用“读类型”串起隔离级别、MVCC 与锁事务隔离级别规定的是并发事务之间的数据可见性。脏读、不可重复读和幻读则分别描述读取未提交值、同一行值变化和结果集增减三类异常。InnoDB 默认使用 RR但 RR 的能力并不只来自一张快照快照读依靠 MVCC、undo log 和 Read View选择对当前事务可见的数据版本。当前读依靠记录锁、Gap Lock 和 Next-Key Lock保护正在读取或修改的索引记录与间隙。以后再遇到“RR 会不会幻读”这类问题可以先问三个问题当前语句是快照读还是锁定读、更新或删除SQL 实际使用了哪个索引扫描并锁定了哪些记录和间隙同一事务是否混用了旧快照与较新的当前状态理解 InnoDB 事务隔离的关键不是死记哪一格打勾而是判断一条语句读取哪个版本、是否加锁以及锁住了哪段索引范围。本文涉及的实现边界可以继续参考 MySQL 8.4 官方文档Transaction Isolation LevelsConsistent Nonlocking ReadsLocking ReadsPhantom RowsInnoDB Multi-Versioning

相关新闻

Unity/Unreal纹理性能优化七步法:从加载卡顿到帧率翻倍的实战指南

Unity/Unreal纹理性能优化七步法:从加载卡顿到帧率翻倍的实战指南

1. 项目概述:纹理性能优化的核心价值在Unity和Unreal Engine这类主流游戏引擎的开发中,尤其是在移动端或性能受限的平台,纹理资源往往是导致性能瓶颈的“头号元凶”。很多开发者都遇到过这样的场景:场景加载时卡顿数秒&#xff0c…

2026/10/21 3:23:15 阅读更多 →
STM32F103外部中断(EXTI)配置详解:从轮询到中断的嵌入式开发进阶

STM32F103外部中断(EXTI)配置详解:从轮询到中断的嵌入式开发进阶

1. 从“轮询”到“中断”:为什么你的STM32程序需要它 如果你刚开始玩STM32,尤其是经典的“蓝桥杯”神片STM32F103,你写的第一个程序大概率是让一个LED灯闪烁。这个程序通常长这样:在一个 while(1) 的死循环里,先点亮…

2026/10/19 16:39:32 阅读更多 →
DC-DC转换器自举电容:原理、计算与PCB布局实战

DC-DC转换器自举电容:原理、计算与PCB布局实战

1. 项目概述:DC-DC转换器中的自举电容 在设计和调试DC-DC降压(Buck)转换器时,很多工程师,尤其是刚接触电源设计的朋友,常常会遇到一个看似不起眼却至关重要的外围元件——自举电容。你可能已经按照数据手册…

2026/10/20 10:05:45 阅读更多 →

最新新闻

论文数据炼金术:AI辅助从提取到清洗的全流程实战

论文数据炼金术:AI辅助从提取到清洗的全流程实战

写在前面:这篇东西不是软件广告,也不打算把某个工具吹成神话。它就来自我自己平时处理论文数据的一堆真实经历:从抓取文献里的零散数据,到清洗成一张能看能算的表,再到最后写出方法学和图表描述,中间踩过的…

2026/10/21 4:51:55 阅读更多 →
DeepSeek国产算力适配实战:从选型到部署的避坑指南

DeepSeek国产算力适配实战:从选型到部署的避坑指南

最近帮几家企业客户把 DeepSeek 系列模型搬到国产算力平台上,前前后后折腾了小一个月。这篇文章不是讲 DeepSeek 有多强——这些大家在官网和论文里都看得到。我想把国产算力适配过程中踩过的坑、筛选过的方案、做过的取舍判断写出来,给正在规划政务或企…

2026/10/21 4:51:55 阅读更多 →
视觉SLAM入门实战:ORB-SLAM2从零复现与避坑指南

视觉SLAM入门实战:ORB-SLAM2从零复现与避坑指南

1. 为什么值得花时间啃下这个视觉SLAM demo如果你正在做机器人、自动驾驶或者AR相关的开发,大概率绕不开一个词:视觉SLAM。而ORB-SLAM2,就是这个领域里被引用最多、工程完成度最高的开源方案之一。它用单目、双目或RGB-D相机就能实时算出相机…

2026/10/21 4:51:55 阅读更多 →
SSA-LSTM组合优化:单输入单输出时间序列预测实用指南

SSA-LSTM组合优化:单输入单输出时间序列预测实用指南

先明确一点:很多人一看到“优化算法 深度学习”就习惯性绕着走,觉得这是学术论文里才有的东西。实际上,把麻雀搜索算法(SSA)和LSTM网络结合这个思路,工程落地价值非常大,尤其是做单输入单输出&…

2026/10/21 4:51:55 阅读更多 →
战地风云2042 dll加载失败排查与修复全流程

战地风云2042 dll加载失败排查与修复全流程

打开战地风云2042时突然弹出一条dll加载失败的提示,很多人第一反应是赶紧搜这个dll的名字,找个网站把它下载下来,塞进系统目录里。这个动作我劝你千万别做,十次里有八次会把自己电脑弄出更大的问题。作为玩PC游戏快十年的老玩家&a…

2026/10/21 4:51:55 阅读更多 →
基于React18与TypeScript构建完整业务后台管理面板的实战复盘

基于React18与TypeScript构建完整业务后台管理面板的实战复盘

最近我在梳理手头的一个前端练习项目时,发现一个很有意思的代号挂在文件夹里——rea。它是我用 React 生态完整搭起来的一个业务管理面板 Demo,用来模拟日常后台系统中最高频的那些场景:列表展示、筛选搜索、数据详情、状态流转、主题切换等等…

2026/10/21 4:50:55 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起:用户到底在追求什么“封号重开”这四个字,放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流,背后是大量的时间和精力成本。正常情况下,没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”,而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时,就意识到:单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →