xtrabackup实战:从物理备份原理到生产环境恢复全流程
1. 为什么说xtrabackup比mysqldump快一个量级先聊个很多人问过我的问题明明mysqldump用得好好的为什么要折腾xtrabackup答案是数据量。我最早接触xtrabackup是在某电商业务的订单库里当时单实例数据量大概200GBmysqldump跑一次全量备份要4个多小时中间业务还得承受不小的IO压力。后来换到xtrabackup同样的库全量备份时间压缩到20分钟左右恢复也快得离谱。这个快10倍不是营销话术而是物理备份和逻辑备份在机制上的天然差距。mysqldump属于逻辑备份它做的事情是把每一行数据以INSERT语句的形式读出来再写出去。200GB的库实际SQL文本体积往往比数据文件本身还大而且每行数据都要经过SQL解析、类型转换、字符集处理整个过程全是单线程的不写并行参数的话速度自然上不去。更麻烦的是对InnoDB表做一致性备份时mysqldump依赖的是可重复读事务隔离级别下的长事务快照备份期间undo log不断保留库越大、备份越久undo膨胀得越厉害。xtrabackup就不一样了。它是物理备份工具直接拷贝数据文件.ibd、表结构定义.frm或字典信息、redo log等一系列二进制文件。文件拷贝本质上是磁盘到磁盘的搬运不需要逐行解析数据所以速度能到磁盘吞吐量的上限。你看到的快10倍往往还是保守说法在SSD上用并行拷贝百GB级别的库跑出几十倍于mysqldump的速度并不稀奇。这里还要提一个关键概念逻辑备份和物理备份在恢复阶段的开销也不同。mysqldump恢复本质上是重放SQL一亿行数据就要执行一亿次INSERTxtrabackup恢复则是把文件放回数据目录再通过redo log做崩溃恢复这步和MySQL实例正常启动时做崩溃恢复的机制完全一致速度远快于SQL重放。所以一句话总结选型逻辑几百MB的小库、开发环境、数据迁移、表结构导出mysqldump依然好用但凡是正经生产库、数据量上TB或百GB、对恢复时间有SLA要求物理备份是唯一靠谱的选择。2. xtrabackup的核心原理它怎么做到不锁表还一致很多人第一次用xtrabackup时有个疑惑备份过程中业务还在写数据文件拷贝出来怎么可能是一致的要讲清楚这个得从InnoDB的底层机制说起。InnoDB是支持崩溃恢复的引擎数据页在内存中被修改后并不一定立刻刷盘而是先写redo log再把脏页异步刷到磁盘。xtrabackup巧妙的地方在于它充分利用了这套机制。2.1 文件拷贝阶段的时间差处理xtrabackup启动后会先记录当前InnoDB的redo log位置然后开始并行拷贝数据文件。拷贝期间MySQL依然正常运行数据文件可能被后台线程持续写入所以拷贝出来的数据文件其实是某个时间段内的混合状态——有些页是旧的有些页是新的有些页可能拷贝到一半就被改写了。这听起来像是一堆破铜烂铁但xtrabackup有后手它在拷贝数据文件的同时会启动一个后台线程持续监听并拷贝redo log。从备份开始那一秒起所有新产生的redo log都会被单独存档。这样备份结束时你手里有一份不完整的数据文件加从备份起点开始的完整redo log历史。2.2 Prepare阶段让数据文件自洽拷贝完成后xtrabackup会对备份执行prepare操作。这个阶段做的事情本质上和MySQL实例崩溃后重启时做的一样把备份期间收集的redo log回放到数据文件中让所有已提交事务的数据落盘把未提交事务的改动回滚掉通过undo log最终让数据文件达到一个一致且可启动的状态。所以xtrabackup的一致性靠的不是备份期间锁住写入而是**数据文件 redo log重放这套组合拳**。这也是它能在不中断业务的情况下完成近实时备份的根本原因。2.3 备份时间点的语义你还需要理解一个细节prepare完成后的备份对应的是备份过程中某个时间点的数据状态而不是备份命令刚开始执行的那一刻。更准确地说是redo log追平数据文件的最后一个时间点。这意味着如果业务在备份期间有大量写入备份出来的数据可能会比备份开始时刻晚一些但只要业务逻辑对一致性要求不是毫秒级时间点精确这完全不是问题。真正要严格精确到某个秒级时间点比如误删数据需要闪回常规做法是结合binlog来做时间点恢复这个后面恢复案例里我会具体讲。3. 生产环境可直接套用的备份命令与脚本原理讲完上干货。下面是我在生产环境里实际跑了一年多的备份方案你拿过去改改路径和参数就能用。3.1 全量备份命令xtrabackup --backup \ --target-dir/backup/mysql/full_$(date %F_%H%M) \ --datadir/var/lib/mysql \ --userbackup_user \ --passwordxxx \ --host127.0.0.1 \ --parallel8 \ --compresszstd \ --compress-threads4 \ --slave-info \ --safe-slave-backup几个参数的选择逻辑--parallel8并行拷贝线程数建议和CPU核数、磁盘吞吐匹配。我实测过从1线程加到8线程备份时间能缩短50%以上但继续加到16线程提升就不明显了因为瓶颈变成了磁盘IO。--compresszstd如果备份目录空间有限在拷贝阶段直接压缩。zstd相比lz4压缩率更高CPU开销也大一点但现代服务器完全扛得住。--slave-info和--safe-slave-backup适用于主从架构。前者会在备份目录里记录主库的binlog坐标和从库的master信息后者保证从库备份时SQL线程落到relay log的位置是安全的。3.2 增量备份命令生产环境我通常每周日做全量备份周一到周六做增量备份。增量备份的原理是xtrabackup可以利用上次备份时的LSN日志序列号只拷贝这个LSN之后被修改的数据页。xtrabackup --backup \ --target-dir/backup/mysql/incr_$(date %F_%H%M) \ --incremental-basedir/backup/mysql/full_20250101_0000 \ --datadir/var/lib/mysql \ --userbackup_user \ --passwordxxx \ --parallel8注意--incremental-basedir指向的是上一次备份的目录可以是全量也可以是增量。xtrabackup会读取上次备份里的xtrabackup_checkpoints文件拿到LSN起点然后只拷贝比这个LSN新的数据页。3.3 一键备份脚本我自己在用的脚本核心逻辑是根据星期几决定全量还是增量然后自动做prepare再发告警通知。#!/bin/bash TODAY$(date %F) BACKUP_BASE/backup/mysql MYSQL_USERbackup_user MYSQL_PASSxxx if [ $(date %u) 7 ]; then xtrabackup --backup \ --target-dir$BACKUP_BASE/full_$TODAY \ --datadir/var/lib/mysql \ --user$MYSQL_USER \ --password$MYSQL_PASS \ --parallel8 \ --compresszstd else PREV_DIR$(ls -d $BACKUP_BASE/*_* | sort | tail -n 1) xtrabackup --backup \ --target-dir$BACKUP_BASE/incr_$TODAY \ --incremental-basedir$PREV_DIR \ --datadir/var/lib/mysql \ --user$MYSQL_USER \ --password$MYSQL_PASS \ --parallel8 \ --compresszstd fi # 归档完成后对最新备份做一致性检查 if [ -d $BACKUP_BASE/full_$TODAY ]; then xtrabackup --prepare --target-dir$BACKUP_BASE/full_$TODAY fi这里有个细节要注意脚本里对增量备份做了prepare吗其实没有特别处理。因为增量备份如果要用于恢复必须按全量 每个增量依次apply的方式做prepare这个动作一般是恢复时才做而不是备份完就做。备份机上只做全量的prepare是为了日常校验备份的完整性防止备份文件本身有问题而没及时发现。3.4 需要定期做的恢复演练备份不验证等于没备份。我强烈建议每个季度至少做一次恢复演练把最近一次备份完整恢复到一台验证机或者同规格的临时实例上跑几个关键查询验证数据完整性。做法很简单恢复机解压备份start MySQL然后执行CHECK TABLE orders, users, products; SELECT COUNT(*) FROM orders;顺便检查一下xtrabackup_info文件里的起点和binlog坐标确认备份没有任何异常。这个习惯能在真正出故障时救你的命。4. 一次真实的生产恢复案例从故障到数据回满的完整链路理论讲完分享一个我实际处理过的恢复案例。某天下午四点多某业务的从库实例突然起不来了错误日志明确显示InnoDB表空间数据页损坏。当时这个实例上承载的不仅是读流量还承担着一份准实时报表的查询业务方不到半小时就开始连环催。接下来说一下我的排查链路和恢复步骤。4.1 先判断主库是否受影响第一时间登录主库检查SHOW MASTER STATUS并检查主库错误日志。确认主库一切正常后判断从库损坏只是局部问题可以直接用备份恢复这个从库不用切换主库风险面小很多。4.2 选择恢复策略从库数据量约800GB最近一次全量备份是两天前的凌晨之后有两次增量备份。我的恢复目标是数据尽量新但恢复时间尽量短。经过评估决定采用全量 两次增量 binlog追平的方案。为什么不直接用MySQL自带的从库重建从主库拉取binlog重放因为从库数据量太大全量重建要重新传800GB文件、再回放一天的binlog耗时预计得四五个小时。而用xtrabackup恢复本地备份并追平binlog大概可以压缩到1小时左右。4.3 处理备份全量prepare 增量合并在备份机上执行# 1. 先将全量备份应用redo log使其达到一致状态 xtrabackup --prepare --apply-log-only \ --target-dir/backup/mysql/full_20250102_0000 # 2. 合并第一个增量备份 xtrabackup --prepare --apply-log-only \ --target-dir/backup/mysql/full_20250102_0000 \ --incremental-dir/backup/mysql/incr_20250103_0000 # 3. 合并第二个增量备份最后一个增量不加apply-log-only xtrabackup --prepare \ --target-dir/backup/mysql/full_20250102_0000 \ --incremental-dir/backup/mysql/incr_20250103_1200三个步骤分别做了什么第一步把全量备份中的已提交事务通过redo log落盘让数据文件自洽。加上--apply-log-only是因为后面还要合并增量不能做回滚动作回滚会破坏后续增量合并所需的状态。第二步把第一个增量备份的数据页合并进全量目录。第三步合并最后一个增量这一步不加--apply-log-only允许执行回滚最终得到一个完整的、可启动的数据目录。合并完成后全量目录里就包含了三天内的全部数据变更。4.4 从xtrabackup_binlog_info里提取binlog坐标做追平备份目录里有个xtrabackup_binlog_info文件记录着备份对应到主库的binlog文件名和偏移量。找到最后合并的那个增量备份对应的坐标然后从主库或备份binlog里把从这个坐标开始的binlog同步到恢复机上再按时间点回放。假设坐标是mysql-bin.000123:456789说明从这个位置之后的binlog事件需要补放。这一步能让从库追上故障前几秒的数据状态而不是停留在最后一次增量备份的时间点。4.5 替换数据目录并启动实例把恢复机的数据目录替换为合并后的备份修改权限启动MySQLchown -R mysql:mysql /var/lib/mysql service mysql start启动完成后我还做了一轮数据一致性比对抽取几张核心表的行数和关键字段的汇总值和主库做对比确认无误后把从库重新挂回主从复制让它自行追平这段时间主库新增的binlog。整个恢复过程从发现问题到业务恢复大约用了50分钟。如果当时没有xtrabackup备份而是用的mysqldump仅恢复时间就可能要三到四个小时更不用说还要面对SQL回放过程中的各种中断风险。5. 这些坑我踩了不止一次xtrabackup使用注意事项工具虽好但用不对地方照样翻车。下面这些坑全是我自己踩过或者在别人的事故复盘里见到过的逐条给你拆清楚。5.1--prepare和--rebuild-indexes别搞混常见误区认为prepare之后备份就能直接用了。事实上prepare分两阶段redo应用和undo回滚xtrabackup会自动完成但如果你对备份文件对应的时间点有要求就必须搞明白prepare过程会影响备份的数据语义。另外如果备份时用了--compress恢复前必须要先解压。解压命令for bf in $(find /backup/mysql/full_20250102_0000 -name *.zst); do unzstd $bf done否则MySQL数据目录里躺着一堆压缩文件实例根本起不来错误日志只会提示table doesnt exist很多人在这上面卡很久。5.2 增量备份链别乱接增量备份必须基于同一条备份链全量 - 增量1 - 增量2中间不能跳链。有些人为了省事用上一次增量作为基于目录来继续做增量这没问题但恢复时必须从全量开始按顺序合并每一个增量顺序不能乱。还有一点增量备份一旦合并到全量目录原备份文件也不要立刻删除恢复确认成功之前保留原始备份链是底线。5.3 binlog坐标不是什么时候都在默认情况下xtrabackup只会记录备份开始时的binlog坐标。如果你开启了GTID模式并且备份的是从库记得加--slave-info否则恢复出来的从库可能无法正常接入复制。主库备份则务必确认log_bin和log_slave_updates是开启的不然做时间点恢复时你根本没有binlog可以追。5.4 备份本身也要考虑IO影响xtrabackup快但它毕竟是密集IO操作。生产环境备份尽量避开业务高峰期。我通常安排在凌晨2点到5点之间并且用--throttle限制IO速率xtrabackup --backup --throttle200 \ --target-dir/backup/mysql/full_$(date %F) \ --datadir/var/lib/mysql--throttle200表示每秒最多IO请求次数可以按磁盘能力调整。如果你发现备份期间业务慢查询明显变多优先调这个参数而不是降低并行度。5.5 版本兼容性是最大的隐藏雷xtrabackup服务端和客户端版本不匹配、或者备份的MySQL版本和恢复机版本不一致都可能出现启动失败的情况。强烈建议备份机和恢复机使用同一个xtrabackup版本记录备份时对应的MySQL小版本恢复时尽量部署同版本或更高小版本的实例升级MySQL小版本前先做一次完整备份不要依赖老备份去跨大版本恢复。我遇到过最头疼的一次是某同事用8.0的xtrabackup去备份5.7的实例结果恢复出来的数据字典一堆报错。后来统一在备份脚本里加了版本检测不符合就报警这种问题才算彻底杜绝。5.6 别忘了定期清理老备份备份策略再完美磁盘撑爆了也白搭。我的清理策略是全量备份保留4周增量备份保留2周每月1号把上个月的备份归档到冷存储后删除本机文件。清理顺序一定是先归档再删除而且要盯着磁盘水位别等满了才想起来处理。6. 一些经验沉淀从备份工具到备份体系的思考最后聊点超出工具本身的体会。xtrabackup只是备份这一环的加速器但真正让一个数据库系统扛得住事故的是完整备份体系。什么叫体系至少要包括三个层面第一是备份的验证机制。备份做完了不等于能恢复我每季度做恢复演练每月的归档备份也抽一部到测试机做启动检查。这件事不能图省事因为这可能是你唯一能在灾难来临前发现问题的手段。第二是恢复的预案和演练。备份工具好不好用得在真正的恢复情形里检验。建议你至少把下面几个场景走一遍数据文件损坏怎么恢复、误删了一张表怎么通过备份binlog恢复、从库落后太多怎么用备份补上。每个场景写一个标准操作文档标注关键参数和验证步骤。第三是监控告警不能只盯着备份成功率。备份失败当然要告警但你还要监控备份耗时、备份文件大小、备份目录所在磁盘的空间余量。备份耗时突然拉长往往意味着数据库本身出了问题备份文件大小异常变化也可能是表空间膨胀或删除操作异常。用一句话概括我的经验xtrabackup解决的是备份多快、恢复多快的问题但能不能恢复、恢复得好不好取决于你平时有没有把恢复当成一等公民来对待。如果你正准备在生产环境引入xtrabackup我的建议路径是先在测试环境跑通全量备份和恢复再切增量再跑两次故障演练然后才轮到生产库。整个过程不复杂但每一步都不能省。等你在真刀真枪的故障里靠着它把数据捞回来你会觉得当初折腾这工具的时间花得太值了。

相关新闻

个人笔记数据库选型:从纯文件到SQLite的实践与避坑指南

个人笔记数据库选型:从纯文件到SQLite的实践与避坑指南

如果你也和我一样,折腾过至少三款笔记软件,最终发现还是自己动手搭建最舒坦,那么这篇关于个人笔记数据库选择的总结应该对你有用。最近我把自己的个人笔记系统从纯 Markdown 文件方案迁移到数据库方案,在数据库选型上花了不少时间…

2026/10/17 18:15:13 阅读更多 →
基于FM33A0xx的FreeRTOS移植:Cortex-M0+内核关键机制解析

基于FM33A0xx的FreeRTOS移植:Cortex-M0+内核关键机制解析

简介:面向嵌入式开发者的FreeRTOS移植参考工程,基于复旦微FM33A0xx系列Cortex-M0平台,解决实时任务调度、中断管理与外设驱动适配等底层问题。压缩包共527个文件、约9.28MB,主体为105个h头文件与90个c源代码,覆盖FreeR…

2026/10/16 10:31:19 阅读更多 →
Spring Boot反射调用Service导致依赖注入为null?从容器取Bean才是正解

Spring Boot反射调用Service导致依赖注入为null?从容器取Bean才是正解

接手过一个老项目,里面有个模块是用反射去调Service层方法的。跑起来一调用就空指针,日志里一看,Service里的Dao接口全部是null。排查了半天,问题本质不是反射写错了,而是Spring容器压根没管你反射new出来的那个对象。…

2026/10/16 10:30:18 阅读更多 →

最新新闻

Kafka接入AI实战:从消息管道到智能数据管道

Kafka接入AI实战:从消息管道到智能数据管道

我这两年跟Kafka打交道的时间,比跟家人说话的时间都多。群里每天都在问“Kafka宕机怎么办”“消费延迟怎么查”,说实话,这些问题翻来覆去就那几个套路。但最近画风变了,大家开始问“Kafka能不能直接把消息丢给大模型处理”“AI能不…

2026/10/17 22:20:22 阅读更多 →
Vue3全栈源码实战:TypeScript类型系统如何撑起可维护链路

Vue3全栈源码实战:TypeScript类型系统如何撑起可维护链路

简介:这份原创源码资料面向希望深入掌握 TypeScript 类型系统并落地 Vue3 全栈项目的前端进阶开发者,围绕高级类型编程、装饰器、全局声明文件等核心知识点展开,同时提供一套 Vue3 TS Pinia 的完整基础项目,可用于工程化实践与项…

2026/10/17 22:20:22 阅读更多 →
萤火商城v2.0.8多端版商业落地:一套代码支撑小程序、H5和App

萤火商城v2.0.8多端版商业落地:一套代码支撑小程序、H5和App

简介:萤火商城 v2.0.8 多端版是一套面向中小型电商创业者、独立开发者与二次开发学习者的轻量级前后端分离电商系统源码,支持微信小程序、H5、公众号与 APP 多端运行,前后端代码 100% 开源,所见即所得,便于快速搭建个性…

2026/10/17 22:20:22 阅读更多 →
微信小说平台CDMS系统实战:从分账结算到避坑指南

微信小说平台CDMS系统实战:从分账结算到避坑指南

简介:草莓CDMS内容分销系统v1.0是一套基于ThinkPHP5.1、EasyWeChat4.0、Swoole和Redis开发的原创内容分销系统,主要面向需要搭建微信小说、漫画、有声等内容平台的技术人员与运营团队。系统提供微信开放平台扫码授权与公众平台手动对接两种模式&#xff…

2026/10/17 22:20:22 阅读更多 →
Flink SQL中ROW_NUMBER() OVER窗口报错解析与避坑指南

Flink SQL中ROW_NUMBER() OVER窗口报错解析与避坑指南

1. 从一个真实报错说起:ROW_NUMBER() 为什么在 Flink SQL 里“水土不服”第一次在 Flink SQL 里写ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)去重的时候,我满心以为这就是个标准 SQL 操作,结果提交作业直接给我甩了个异常&#xff0…

2026/10/17 22:20:22 阅读更多 →
Cline 配置 Figma MCP 服务器(Windows):把 settings 改到 TaoToken 的完整步骤

Cline 配置 Figma MCP 服务器(Windows):把 settings 改到 TaoToken 的完整步骤

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

2026/10/17 22:19:21 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 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/16 21:54:38 阅读更多 →
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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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/16 14:23:36 阅读更多 →