ClickHouse性能调优实战:从system库到工具组合的全链路指南
1. 调优第一步把ClickHouse自带的system库当成第一套性能工具箱接手ClickHouse集群运维的第一年我踩得最深的一个坑就是服务变慢之后不知道从哪儿下手抓原因。那时候我习惯先看机器CPU和内存再翻日志折腾半小时还是没头绪。后来我才意识到ClickHouse自己就藏了一套完整的性能观测体系就是内置的system库。说得直白一点ClickHouse每个节点上都有几十张system开头的数据字典表它们就像飞机驾驶舱里的仪表盘把查询日志、线程状态、合并任务、内存分配、配置动态变化全部记录在案。你不需要装任何额外服务只要会写SQL就能做最基础的性能归因。1.1 用query_log定位慢查询一条SQL就能看清全链路在ClickHouse里做性能调优第一件事永远是看system.query_log。这张表记录了每一条执行过的查询包括查询ID、用户、查询文本、开始和结束时间、实际耗时、读取的行数、读取的字节数、内存峰值、是否命中缓存等。我用它定位慢查询的姿势很简单过滤最近一小时耗时最久的前十条SELECT query_id, query, query_duration_ms, read_rows, read_bytes, memory_usage, rows_before_limit FROM system.query_log WHERE event_time now() - INTERVAL 1 HOUR ORDER BY query_duration_ms DESC LIMIT 10;这条查询的价值在于它同时给出了“耗时”和“读取量”。如果一条查询耗时20秒但read_rows只有几千行那问题大概率出在计算环节比如复杂的聚合、字符串函数、不合理的JOIN如果read_rows高达几亿说明扫描数据量太大这时候的优化方向就应该是分区裁剪、主键索引和物化视图而不是去调并发线程数。这两个方向搞反了后面做多少调优都是白费。实际排障时我还会把query_duration_ms和memory_usage字段抓出来一起看。有些查询开始只跑了2秒但内存峰值冲到了50GB最终因为内存超限被强制杀掉。这种情况下真正的问题不是慢而是内存预算不够后续的优化思路就完全不同了。query_log的优势还在于它有持久化能力ClickHouse默认会保留一定天数的历史日志你可以按天对比同一类查询的耗时变化这在做发版后性能回归时特别有用——一个新索引字段上线前是什么耗时段上线后又是什么耗时段用一条聚合SQL就能量化出来。1.2 用system.metrics和system.processes盯实时负载慢查询分析是事后复盘但现场出问题时你需要的是“当下正在发生什么”。system.processes可以看作当前正在运行的查询的实时快照它和query_log的字段高度相似但只显示还没执行完的语句。我常用的排查组合是先看有没有大查询正在运行再看它的具体阶段。SELECT query_id, elapsed, query_duration_ms, read_rows, read_bytes, memory_usage, query FROM system.processes WHERE elapsed 5 ORDER BY elapsed DESC LIMIT 20;如果你的集群突然CPU飙升这条SQL能立刻告诉你是不是某个大查询正在横扫全表。与此同时system.metrics保存的是各种累计计数器和当前值比如正在执行的查询数、合并任务数、被拒绝的插入次数、网络流量等。我比较关注三个CurrentAsyncTasks、Merge正在占用的协程数量、以及MemoryTracking整体容量。还有一个容易被忽略的system.asynchronous_metrics它是后台线程定期采样的节点级指标包含物理内存使用量、不同桶级别的内存分配量等。配合查询日志你基本能画出一条“哪个查询在哪段时间吃了多少内存”的时间线。我在早期排查过程中发现很多时候系统负载报警不是因为CPU太忙而是因为多个查询同时触发了大排序内存挤爆之后触发了OOM保护反而把CPU拖垮了。只看云监控面板的CPU曲线永远看不到这一层但把system.processes和异步指标放在一起看十几秒就能定位到原因。1.3 性能基线管理没有对比调优就没有意义很多人拿到query_log就很兴奋上来就找最慢的查询改改完发现没效果因为缺少参照物。性能调优这件事必须建立在基线之上在没有任何改动之前先把你核心查询的P50、P90、P95耗时记录下来然后再动配置或改SQL。改完同样跑一遍看百分位有没有下降波动是否变大。我用clickhouse-benchmark配合query_log一起做基线。具体做法是先把生产环境的慢查询样本导出到一个文件然后在低峰期对测试集群跑一轮全量回放取耗时分布调优之后再做一轮对比两个分布的差值。在query_log里还可以用分位数函数做常规聚合基线例如按小时统计每条模板类查询的avg、p90耗时。这样做的最大好处是防止“优化了一个查询拖慢了其他查询”的假象因为基线是全量视角不是单点视角。记住一个原则性能调优的产出必须是一组可对比的数字而不是“感觉变快了”。2. 从SQL到Dashboard四类ClickHouse性能调优工具怎么选system库解决了“有没有数据看”的问题但日常监控和深度分析还需要外部工具。我把实际用过、验证过有效的ClickHouse调优工具分成四类查询诊断工具、可视化监控组件、基准测试工具、整合型调优平台。每一类解决不同阶段的问题没有哪个工具能包办一切。工具类别典型工具主要解决什么问题适用阶段查询诊断EXPLAIN ANALYZE、EXPLAIN SYNTAX、query_log单条慢查询的内部执行细节日常分析可视化监控Prometheus clickhouse-exporter Grafana集群整体指标趋势、告警持续观测基准测试clickhouse-benchmark、自定义压测脚本量化优化效果、容量评估优化前后对比整合平台托管服务自带的可观测面板自动告警、细粒度追踪、团队共享生产环境长跑2.1 第一类查询诊断工具EXPLAIN ANALYZE是慢SQL的放大镜如果一条查询在query_log里被标记为慢下一步就是把它单独拿出来做执行计划分析。EXPLAIN ANALYZE是ClickHouse里最常用的诊断原语它会真实执行当前查询并把每一步的数据读取量、耗时、线程数、过滤后行数等细节打印出来。我不会在生产高峰直接跑而是在专门的诊断时段或者带上LIMIT。EXPLAIN ANALYZE SELECT region, count(), sum(amount) FROM events WHERE ts 2024-01-01 GROUP BY region;输出里我最看重的字段是每个步骤读取的行数和耗时占比。比较常见的现象是整个查询80%的时间都耗在底层数据读取说明索引裁剪没生效分区修剪做得好不好一眼就能看出来。另一个高频场景是Expression步骤耗时异常很可能是因为select里写了大量的字符串处理函数、正则匹配或JSON解析这些函数是CPU杀手最简单的验证方式就是把它们拆出去对比执行时间。EXPLAIN SYNTAX则用来检查查询重写它不做实际执行只输出优化器重写后的逻辑计划。尤其是在优化子查询、常量折叠、IN子查询改写时用它确认优化器有没有按你预期的方式执行。很多人在调优时只盯着配置项却忽略了执行计划里暴露出的索引失效问题这其实是最便宜、最直接的突破口。2.2 第二类可视化监控组件一套组合拳就能上生产单靠命令行查system表没法做趋势分析和告警我在集群规模超过20个节点之后就强制要求接上Prometheus加Grafana这套组合。clickhouse-exporter会把ClickHouse暴露的prometheus指标抓出来node_exporter负责收集节点硬件指标最后统一汇总到Prometheus再由Grafana出图。这套组合最实用的场景是“容量水位预判”。我建立了一套生产告警规则CPU使用率超过85%持续5分钟、可用内存低于总内存15%、以及后台合并队列堆积超过阈值分别对应三类不同风险。ClickHouse的远程监控还提供metrics接口直接暴露query、merge、replication等关键计数器和吞吐量指标你可以按月观察这些趋势线判断当前配置是跟得上业务增长还是已经逼近上限。如果每天都在报警说明该扩容或者梳理查询优先级了。在仪表盘的组织上我建议至少放四类视图第一类是集群总览展示节点数量、副本延迟、最大连接数第二类是查询质量展示P50/P90耗时、错误率、Top慢SQL第三类是后台活动展示合并线程池使用率、replicated log延迟第四类是资源层展示磁盘空间增长趋势和内存分配压力。2.3 第三类基准测试工具clickhouse-benchmark给优化效果打分clickhouse-benchmark是ClickHouse自带的压测工具逻辑很简单反复执行一批SQL然后输出耗时分布。我把它用在三个地方一是上线新查询前做性能验证确认这条SQL放到生产环境不会把集群拖垮二是做优化前后对比用数字说服自己和团队三是做容量规划估算当前硬件在并发访问下的极限支撑能力。基本用法如下clickhouse-benchmark \ --host127.0.0.1 \ --concurrency16 \ --iterations1000 \ --query SELECT avg(amount) FROM events WHERE ts 2024-01-01看输出时我重点关注的是P90和P99分位数而不是平均值。因为平均值很容易被几条慢查询抬高生产环境的体验主要由长尾决定。另外并发数从4加到32同一个查询的P90如果从600毫秒直接翻到5秒说明这个查询的资源消耗已经很重跟你个人感觉的“这条SQL挺快”完全不是一回事。基准测试的价值也在于此它可以暴露串行执行看不出的资源争用问题。2.4 第四类整合型调优平台适合团队协作和长期观测当系统规模大起来之后细分工具带来的碎片化问题开始影响协作效率需要一个整合的看板把所有数据集中展示。托管服务自带的可观测面板是我在实际项目中用得最省心的选项它天然带ClickHouse的指标定义、常见告警模板和历史曲线存储不需要自己维护exporter和dashboard。对大多数团队来说用开源自建组合还是托管看板判断依据不在于哪个更“高端”而在于运维人力是否够用。如果团队只有一两个人负责上百个节点选择整合型平台能节省极大量的时间。当然整合平台也有短板就是定制化程度相对有限。比如你想在某个查询事件发生时自动拉取query_log快照做二次分析这种偏自动化的调优工作流自建Prometheus加脚本反而更灵活。大多数情况下我的建议是小规模集群用system库加Grafana就够了大规模或者多人协作时才考虑整合平台没必要一上来就上最重的方案。3. 三个高频调优场景的工具组合打法工具架好了关键得会用。我把自己实际处理过的高频调优场景整理成三组每组都给出工具组合、排查路径和最终效果你们可以对号入座。3.1 场景一查询从毫秒级劣化到分钟级某天收到业务反馈原先几十毫秒返回的报表查询现在要跑一分多钟。我先用query_log按耗时排序锁定查询文本发现它是一张累计超过30亿行的行为明细表上的聚合查询。接着用EXPLAIN ANALYZE执行一遍观察读取量这条查询明明有条件ts now() - INTERVAL 1 DAY但read_rows显示它扫了整整十几亿行说明分区裁剪完全失效。再检查表结构才看到这个表的分区键是toYYYYMMDD(ts)但查询条件里用的是ts字段按道理应该能裁剪。问题出在查询里把ts包了一层函数导致分区裁剪被阻断。修复方案是把查询条件里的函数去掉或者改成toDate(ts)的范围写法让优化器能走分区裁剪。改完后read_rows从十几亿掉到几十万查询耗时回到300毫秒左右。整个排查过程用到三个工具query_log找SQL、EXPLAIN ANALYZE看读取量、以及分区键设计审查。这个场景说明工具只能帮你暴露问题最终解决还是要靠对数据分布和索引机制的理解。3.2 场景二CPU被打满查询互相挤占另一个经验是CPU被打满时不要急着加机器。有一次某个20节点集群的CPU持续到95%以上但单看每一条查询都不算慢问题出在并发查询数量过多。我先用system.processes看当前有多少查询在跑发现同时跑了七八十个小查询每个查询虽然只读几百兆数据但加起来把CPU计算资源完全占满了。这类问题的排查工具是system.processes加query_log的并发统计。我的处理是两层一方面在应用层对ClickHouse增加查询并发数限制避免同步任务一次涌入过多查询另一方面根据查询的优先级配置不同资源池。ClickHouse的memory_tracker能够限制内存用量配合max_execution_time和max_concurrent_queries参数给慢查询压上刹车。优化之后CPU使用率稳定在70%以下虽然单条查询的耗时略有上升但整体吞吐明显变好报表在高峰期也不会再卡住。这个场景教会我调优不能只追求单条SQL最快要追求整个集群的吞吐和稳定性。3.3 场景三合并线程太猛拖累线上查询ClickHouse的MergeTree引擎会在后台持续做数据合并合并的过程中会占用相当的CPU和IO资源。我遇到过一次白天高峰期合并任务扎堆导致写入变慢、查询延迟增加的情况。通过system.merges可以查看正在进行的合并任务明细结合后台日志能看出是不是分区数量设置不当导致小分区过多需要高频合并。工具组合上我用system.merges定位堆积用parts表看分区数分布再用配置参数限制合并并发。临时处理是先降低后台合并线程数把资源让给查询根治方案是调整分区粒度让分区数据量保持在合理范围减少无谓合并。调整后最直观的效果是白天高峰期的查询P95从3秒降到800毫秒。这个例子说明clickhouse的调优往往不是单向改SQL后台任务和前端查询之间的资源均衡才是长期稳定的关键。4. 用过几十次之后我总结的避坑清单和心得这个章节写的是我在真实运维ClickHouse过程中反复踩过、之后也看别人踩过的坑还有几条被验证有效的经验。工具本身不会替你决策但知道怎么避免被工具误导会减少大量无效调优。4.1 工具容易误导你的几个瞬间第一个坑是只看系统CPU指标忽略了查询读取行数。CPU高不一定代表计算瓶颈也可能是内存带宽、磁盘IO或CPU缓存命中率的问题。我见过一个查询全是长字符串拼接CPU长时间100%但你把它改成只select需要的列CPU使用率立刻降下来因为大量CPU花在了读取无用列上。另一个坑是query_log里的query_duration_ms只是服务端处理时间不包含客户端网络传输耗时。如果业务反馈“查询很慢”但query_log显示很快就要去看网络连接、客户端序列化、以及是否在应用层做了大量后处理。第三个坑是不加筛选地乱跑EXPLAIN ANALYZE在生产环境执行未限流分析的时候可能把集群打爆。我建议所有诊断性EXPLAIN都先加LIMIT明确标注诊断环境避免误伤生产。4.2 容易被忽略的非查询维度ClickHouse的性能不只是SQL优化还包括很多认知盲区。以写链路为例很多人只关注查询慢但写入堵塞同样会拖垮查询。写入过程中如果出现大批小文件的插入CREATE临时目录和fsync阻塞都会占用大量IO。我常用的检查方式是通过system.parts查看表的分区数和part数量如果单个part数量过多就说明写入频率和合并策略需要调整。磁盘空间也有讲究。ClickHouse的性能对磁盘IO非常敏感机械盘和SSD的差距不在随机读写而在顺序扫描的吞吐。当磁盘的可用空间低于总容量15%时merge任务会明显变慢空间不足还会触发只读保护。另外网络吞吐对于分布式查询也极其关键如果某个查询涉及跨节点读取网络延迟会直接放大到整体耗时里。所以我的习惯是调优时先看硬件层再看配置层最后才看SQL层。顺序反了经常会事倍功半改了一堆配置但硬件本身已经到瓶颈等于白忙。4.3 给新手的调优工具落地路径如果你刚刚开始负责ClickHouse集群我的建议是不要试图一次性上齐所有工具。先花两周时间把system.query_log和system.processes用熟你能解决70%的排障问题。等遇到需要趋势分析的场景再考虑接Prometheus和Grafana不要第一天就搭一整套监控体系那样你连什么指标正常、什么指标异常都没有概念。接下来可以尝试用clickhouse-benchmark给自己的核心业务查询做一份压测报告顺便把常见告警规则沉淀成文档。第三个月之后再考虑是否需要更重的整合型平台。工具的价值永远取决于使用者的判断力一个会用system库的老手能比一个装了一堆工具的新手更高效地解决ClickHouse性能问题。在我个人负责过的集群里真正让性能发生质变的一次反而只是改掉了分区键上的函数写法连一行配置都没动。工具推荐得再多最终落脚点还是要回到对数据特征和执行机制的理解。先把最基础的工具用扎实再谈进阶调优这条路我亲身验证过走得通。

相关新闻

AI编码工具冲击下,程序员真正的护城河到底是什么?

AI编码工具冲击下,程序员真正的护城河到底是什么?

大约是半年前,我接到一个订单中心模块的重构任务,顺手把一部分“状态流转”的代码丢给了某主流AI编码工具。 当时的心态其实是试探性的,觉得它能生成一个骨架就算成功,毕竟这个模块牵扯到订单取消、超时关单、支付回调、幂等控制…

2026/10/13 8:38:54 阅读更多 →
基于PaddleOCR与PyQt5的GUI车票识别系统设计与实现

基于PaddleOCR与PyQt5的GUI车票识别系统设计与实现

简介:这是一套基于GUI的OCR车票识别完整毕业设计项目,面向计算机视觉、机器学习方向的本科生及入门开发者,重点解决车票日期、车次、座位号等关键信息的自动提取与可视化操作问题。压缩包共82个文件,整体约4.01MB,包含…

2026/10/13 8:37:52 阅读更多 →
Prim算法详解:最小生成树的原理、实现与工程实践

Prim算法详解:最小生成树的原理、实现与工程实践

1. “Prime算法”到底在讲什么先把这个名字聊透。“Prime算法”在计算机与数据结构的语境下,最常被对应到两条线:一条是图论里求最小生成树的经典贪心算法——Prim算法,另一条则是素数(Prime Number)相关的数论算法&am…

2026/10/13 8:37:52 阅读更多 →

最新新闻

Python大数据分析实验通关指南:从数据清洗到可视化全流程

Python大数据分析实验通关指南:从数据清洗到可视化全流程

1. 从lab7这个编号说起:为什么Python课设总在第七周卡住 如果你正在翻这门课的实验安排,看到lab7这个编号,大概率已经过了前六周的语法基础、循环判断、函数封装,开始进入真正要“处理数据”的阶段了。这门课的名字叫《Python程序…

2026/10/13 9:22:51 阅读更多 →
RAG系统实战:从知识库构建到检索生成的全链路设计与调优

RAG系统实战:从知识库构建到检索生成的全链路设计与调优

1. RAG系统到底在解决什么问题第一次接触RAG这个概念是在一个内部知识库项目里。当时团队想把一堆产品文档、会议纪要、客服记录做成一个能问答的系统,第一反应是直接丢给大模型。结果很快就撞墙了:模型对内部信息一无所知,问它某个产品的具体…

2026/10/13 9:22:51 阅读更多 →
AI写的PowerShell脚本一跑就崩?根因分析与实用提示词模板

AI写的PowerShell脚本一跑就崩?根因分析与实用提示词模板

前几天帮朋友排查一个调度任务,卡在一个PowerShell脚本上,AI一口气生成了四十多行“看起来很完整”的代码,结果跑起来第一行就报“术语无法识别”,后面全部白给。这不是我第一次见到AI写的PowerShell翻车了。它给出的命令确实存在…

2026/10/13 9:22:51 阅读更多 →
基于OpenCV的单目与双目相机测距:标定、视差与精度实践

基于OpenCV的单目与双目相机测距:标定、视差与精度实践

简介:一套基于Python和OpenCV的普通相机图像测距系统资料包,面向计算机视觉与立体成像方向的开发者和学习者,解决通过普通相机拍摄图像实现精确距离测量的问题。系统涵盖单目相机标定校正、双目相机空间位置关系标定、左右视图视差计算与深度…

2026/10/13 9:22:51 阅读更多 →
dplyr 值映射重编码:深入解析 `recode_values()` 与 `replace_values()` 的语义、类型安全与防御性编程

dplyr 值映射重编码:深入解析 `recode_values()` 与 `replace_values()` 的语义、类型安全与防御性编程

数据分析 【免费下载链接】dplyr dplyr: A grammar of data manipulation 项目地址: https://gitcode.com/gh_mirrors/dp/dplyr 点击查看 免费下载 recode_values() 与 replace_values() 是 dplyr 1.2.x 引入的"按值匹配"重编码函数族:前者用…

2026/10/13 9:22:51 阅读更多 →
互联网大厂职场生存指南:高薪背后的健康与职业危机

互联网大厂职场生存指南:高薪背后的健康与职业危机

1. 职场生存现状:光鲜背后的残酷现实那天晚上十点半,我正收拾东西准备离开办公室,突然听到隔壁工位传来一阵骚动。老张,那个连续加班三个月的技术总监,突然倒在了键盘上。救护车来得很快,但已经来不及了。一…

2026/10/13 9:21:48 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →