Oracle游标超限(ORA-01000)诊断与解决:从原理到代码修复
1. 问题根源为什么游标会“超限”在Oracle数据库的日常运维和开发中“ORA-01000: maximum open cursors exceeded”这个错误绝对算得上是一个高频“访客”。很多朋友第一次遇到时可能会一头雾水我明明只是执行了几个查询怎么游标就“爆”了呢要彻底解决这个问题我们得先搞清楚Oracle游标到底是什么以及它是如何被“打开”和“关闭”的。简单来说你可以把游标想象成数据库服务器端的一块私有工作区专门用来存放你提交的SQL语句无论是查询还是DML的解析结果、执行计划以及后续处理数据所需的状态信息。当你执行一条SQL时Oracle会在其共享池Shared Pool里寻找是否已经有完全相同的语句被解析过这叫软解析或软软解析。如果没有或者因为某些原因不能重用它就需要为你的会话Session新分配一块区域来“打开”一个游标进行硬解析并存放执行上下文。这里的关键在于“每个会话的私有性”。一个游标被打开后它所占用的资源主要是PGA内存是归属于你这个特定数据库连接的。Oracle为了防止单个会话无节制地消耗服务器内存资源设置了一个会话级别的参数OPEN_CURSORS。这个参数定义了一个会话在同一时刻能够保持“打开”状态的游标数量的上限。注意是“保持打开状态”而不是“累计打开过”的总数。那么什么情况下游标会保持打开状态而不关闭呢最常见的原因就是应用程序的代码逻辑缺陷。在理想情况下一段数据库操作代码应该遵循“打开-使用-关闭”的严格模式。但在实际开发中尤其是在使用连接池、进行循环操作或处理异常时很容易出现游标未被正确关闭的情况。例如在Java中使用JDBC时如果你只调用了Connection.createStatement()或Connection.prepareStatement()而在使用完ResultSet和Statement对象后没有在finally块中或使用 try-with-resources 语句显式地调用.close()方法那么这些游标就会一直保持打开状态直到连接被归还给连接池甚至连接池可能也不会主动关闭它们。随着请求的不断处理这些未被关闭的游标就会逐渐累积最终触达OPEN_CURSORS的上限抛出错误。另一个需要区分的概念是系统级的OPEN_CURSORS和会话级的实际值。OPEN_CURSORS是一个初始化参数它规定了上限。而一个会话当前到底打开了多少个游标是需要动态查询的。有时候即使你提高了上限但代码中的泄漏问题没解决也只是延缓了错误发生的时间治标不治本。2. 诊断与排查定位游标泄漏的“元凶”当错误发生时慌慌张张地去调大参数是最不明智的。正确的第一步是诊断到底是哪个程序、哪段代码、哪种操作导致了游标泄漏我们需要像侦探一样收集现场证据。2.1 实时监控当前会话游标使用情况首先我们需要查看当前出问题的会话以及所有会话的游标打开情况。最直接的查询是使用v$sesstat和v$statname视图。-- 查询当前会话打开的游标数 SELECT a.value AS open_cursors, s.username, s.sid, s.serial# FROM v$sesstat a, v$statname b, v$session s WHERE a.statistic# b.statistic# AND b.name opened cursors current AND a.sid s.sid AND s.sid sys_context(USERENV, SID); -- 当前会话如果你想查看数据库中所有会话的游标打开情况并按数量排序找出“大户”可以这样查-- 查询所有会话按打开游标数降序排列 SELECT s.sid, s.serial#, s.username, s.program, s.machine, a.value AS open_cursors FROM v$sesstat a, v$statname b, v$session s WHERE a.statistic# b.statistic# AND b.name opened cursors current AND a.sid s.sid AND a.value 0 -- 只查看打开了游标的会话 ORDER BY a.value DESC;这个查询结果非常有用。USERNAME告诉你哪个数据库用户PROGRAM如java.exe,TOAD.exe,sqlplus.exe和MACHINE告诉你程序从哪里发起。如果你发现某个特定的应用程序服务器主机MACHINE上的某个程序PROGRAM对应的会话其OPEN_CURSORS值异常高且持续增长那基本就可以锁定问题源头了。注意v$session视图中的PROGRAM信息是由客户端应用程序设置的。像 JDBC Thin Driver 通常会设置成类似JDBC Thin Client的字符串而一些成熟的中间件或客户端工具如 Weblogic、Toad会有更明确的标识。这有助于你快速定位是哪个应用服务出了问题。2.2 深入分析查看具体是哪些SQL占用了游标知道了哪个会话有问题下一步就是看这个会话里到底哪些SQL语句打开了游标却没关。我们可以联合v$open_cursor视图来查看详细信息。这个视图显示了当前所有打开的游标。-- 查询指定SID例如123和SERIAL#例如45678的会话中所有打开的游标及其SQL文本 SELECT sid, sql_text, sql_id, user_name, address, hash_value FROM v$open_cursor WHERE sid 输入SID AND user_name (SELECT username FROM v$session WHERE sid 输入SID AND serial# 输入SERIAL#) ORDER BY sql_text;执行这个查询时系统会提示你输入SID和SERIAL#填入之前查询到的那个高游标会话的对应值即可。结果中的SQL_TEXT字段就是罪魁祸首的SQL语句。你很可能会发现大量重复的SQL语句SQL_ID相同。这通常意味着这段SQL在循环中被反复执行但每次执行后相关的Statement或ResultSet对象都没有被关闭。一个非常重要的实操心得v$open_cursor在某些Oracle版本或配置下可能只显示一部分游标或者显示的信息受SESSION_CACHED_CURSORS参数影响。如果在这里查不到预期数量的游标可以尝试使用更底层但可能带来性能开销的查询或者结合v$sqlarea和v$session的SQL_ID进行关联分析。不过对于绝大多数由应用代码泄漏引起的游标问题v$open_cursor足以提供关键线索。2.3 设置预警通过监控脚本提前发现问题对于生产环境我们不能总是等到错误发生了才去处理。可以编写一个简单的监控脚本定期比如每分钟检查是否有会话的游标使用数超过某个阈值例如OPEN_CURSORS参数的80%并通过邮件或监控平台告警。-- 示例查询游标使用率超过80%的会话假设 OPEN_CURSORS300则阈值是240 SELECT s.sid, s.serial#, s.username, s.program, a.value AS current_open, p.value AS max_allowed, ROUND((a.value / p.value) * 100, 2) AS usage_percent FROM v$sesstat a, v$statname b, v$session s, (SELECT value FROM v$parameter WHERE name open_cursors) p WHERE a.statistic# b.statistic# AND b.name opened cursors current AND a.sid s.sid AND a.value p.value * 0.8 -- 阈值设为80% ORDER BY usage_percent DESC;将这个查询集成到你的运维监控系统中就能实现主动预警在问题影响用户之前就通知到DBA或开发人员。3. 解决方案从临时救火到根治优化诊断清楚后我们就可以分层次地解决问题了。解决方案通常分为“临时缓解”和“根本解决”两个层面。3.1 临时缓解措施调整参数与清理会话当问题突然发生影响到业务时我们需要快速恢复服务。1. 调整会话级 OPEN_CURSORS 参数不推荐作为长期方案首先可以立即在数据库层面调整OPEN_CURSORS参数。但这个参数是静态参数static修改后需要重启数据库才能生效对于紧急情况不适用。-- 查看当前设置 SHOW PARAMETER open_cursors; -- 修改需要重启 ALTER SYSTEM SET open_cursors 500 SCOPE SPFILE;请注意盲目调大此参数是危险的。它只是提高了“水位线”并没有解决泄漏问题。如果泄漏持续存在游标数最终会达到新的上限并且会消耗更多的PGA内存可能引发更严重的性能问题如内存换页。这只能作为为修复代码争取时间的权宜之计。2. 终止问题会话如果定位到了具体的故障会话SID和SERIAL#并且确认该会话可以中断比如是一个后台任务或可重启的应用连接最直接的办法就是杀掉这个会话。-- 强制终止指定会话 ALTER SYSTEM KILL SESSION SID,SERIAL# IMMEDIATE; -- 例如ALTER SYSTEM KILL SESSION 123,45678 IMMEDIATE;执行后该会话持有的所有游标和锁资源会被释放。但要注意如果这个会话正在执行一个重要的事务强制终止可能导致事务回滚对业务有影响。务必先确认会话性质。3. 优化应用服务器连接池配置很多时候游标泄漏发生在应用服务器的数据库连接池中。连接池中的连接被多个请求复用如果某个请求没有关闭游标这个“脏”连接被还给连接池后游标依然存在。当下一个请求拿到这个连接并继续执行操作时就可能累积更多游标。设置连接有效性测试在连接池配置中如HikariCP, DBCP, C3P0开启testOnBorrow或类似的选项配置一个简单的查询如SELECT 1 FROM DUAL在连接被借出时执行。虽然这会增加一点开销但能确保归还的连接是“干净”可用的。缩小连接池大小在保证性能的前提下适当减小连接池的最大连接数。连接数越少发生泄漏时累积的游标总数上限就越低问题会暴露得更早、更明显便于早期发现。设置连接回收机制配置连接池定期检查并关闭空闲时间过长的连接或者强制回收那些活跃时间异常长的连接可能卡住了。3.2 根本解决方案修复应用程序代码这才是治本之策。所有临时措施都是为了给代码修复争取时间。修复的核心原则是确保每一个打开的Statement、PreparedStatement、CallableStatement和ResultSet对象都在 finally 块中或使用 try-with-resources 语法被正确关闭。反面教材Java JDBC// 错误示例游标泄漏 Connection conn dataSource.getConnection(); PreparedStatement ps null; ResultSet rs null; try { ps conn.prepareStatement(SELECT * FROM large_table WHERE status ?); ps.setString(1, ACTIVE); rs ps.executeQuery(); while (rs.next()) { // 处理数据... } // 如果这里发生异常rs和ps将不会被关闭 } catch (SQLException e) { e.printStackTrace(); } // 即使没有异常也忘记了关闭 rs 和 ps // conn 可能被连接池回收但游标还在正确做法1显式在 finally 中关闭经典写法Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn dataSource.getConnection(); ps conn.prepareStatement(SELECT * FROM large_table WHERE status ?); ps.setString(1, ACTIVE); rs ps.executeQuery(); while (rs.next()) { // 处理数据... } } catch (SQLException e) { e.printStackTrace(); } finally { // 关闭顺序ResultSet - Statement - Connection (通常连接由连接池管理不在此关闭) if (rs ! null) { try { rs.close(); } catch (SQLException e) { /* 记录日志 */ } } if (ps ! null) { try { ps.close(); } catch (SQLException e) { /* 记录日志 */ } } // 注意Connection 通常由连接池管理不应在 finally 里直接 close() // 而是调用 conn.close() 将其归还给池。具体取决于连接池的实现。 if (conn ! null) { try { conn.close(); } catch (SQLException e) { /* 记录日志 */ } } }正确做法2使用 try-with-resourcesJava 7推荐这是最简洁、最安全的方式资源会自动关闭。try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(SELECT * FROM large_table WHERE status ?)) { ps.setString(1, ACTIVE); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { // 处理数据... } } // ResultSet 在此自动关闭 } catch (SQLException e) { e.printStackTrace(); } // PreparedStatement 和 Connection 在此自动关闭对于其他语言和环境原则相同Python (cx_Oracle): 使用with上下文管理器。.NET (OracleDataReader): 确保OracleDataReader和OracleCommand在using语句块中或显式调用.Dispose()。PL/SQL: 显式关闭打开的游标CLOSE cursor_name;特别是在循环或异常处理块中。3.3 数据库端优化合理设置相关参数在修复代码的同时DBA也可以从数据库层面进行一些优化提高游标管理的效率减少硬解析从而间接降低游标打开的压力。1. SESSION_CACHED_CURSORS这个参数指定了会话级别可以缓存的关闭游标的上限。如果一个会话重复执行同一条SQLOracle可以将这个游标的上下文信息缓存在会话内存中下次执行时直接重用避免了重新解析软解析的开销也减少了游标“打开-关闭”的频繁操作。适当调大此参数对性能有好处。-- 查看当前值 SHOW PARAMETER session_cached_cursors; -- 修改可以动态修改立即生效 ALTER SYSTEM SET session_cached_cursors 100 SCOPE BOTH;设置多大合适可以观察v$sesstat中session cursor cache hits与parse count (total)的比率。如果命中率低可以考虑增加。2. CURSOR_SHARING这个参数控制游标共享行为。默认是EXACT要求SQL文本必须完全一致包括字面量才能共享游标。对于很多使用动态拼接SQL特别是带不同字面值的应用会产生大量硬解析快速消耗游标。可以将其设置为FORCE或SIMILAR后者更智能但已过时让Oracle将字面量替换为绑定变量从而增加游标共享的可能性。ALTER SYSTEM SET cursor_sharing FORCE SCOPE MEMORY;重要警告CURSOR_SHARINGFORCE是一个全局性的强力干预可能会改变SQL的执行计划在某些复杂场景下可能导致性能下降。强烈建议先在测试环境充分评估并且最根本的解决方案是修改应用程序使用绑定变量来编写SQL。3. OPEN_CURSORS 的最终设定在代码泄漏问题解决后可以根据实际监控数据合理设置OPEN_CURSORS。一个常见的经验法则是设置一个比应用峰值使用量高出 20%-50% 的安全余量。可以通过长期监控v$sesstat中的opened cursors (current)最大值来确定。4. 预防与最佳实践构建游标管理“防火墙”解决了眼前的危机我们更需要建立长效机制防止问题复发。4.1 开发规范与代码审查强制使用 try-with-resources / using 语句在团队编码规范中明确要求所有数据库资源操作必须使用自动资源管理语法。代码审查重点在Code Review时将数据库连接、语句、结果集的关闭逻辑作为必查项。可以使用静态代码分析工具如SonarQube, FindBugs来扫描常见的资源泄漏模式。封装数据访问层避免在业务代码中直接编写JDBC操作。使用Spring JdbcTemplate、MyBatis等成熟的持久层框架它们内部已经很好地处理了资源的打开和关闭。4.2 实施严格的测试压力测试与长时间运行测试在新版本上线前进行充分的压力测试和稳定性测试如24小时持续运行。监控测试过程中数据库会话的游标数变化观察是否有持续增长的趋势。使用诊断工具在测试环境可以利用Oracle提供的跟踪工具如ALTER SESSION SET events 10046 trace name context forever, level 12;来生成详细的SQL跟踪文件分析游标的打开和关闭情况。4.3 生产环境监控与告警如第2.3节所述建立持续的监控。不仅要监控游标使用率还可以监控v$sysstat中的“opened cursors cumulative”累计打开的游标数的增长速度。如果增长速度异常快即使当前使用率不高也预示着可能存在潜在的泄漏或极高的硬解析率。4.4 绑定变量是王道这可能是预防游标相关问题最有效、最根本的实践。永远不要使用字符串拼接的方式来构造SQL语句。// 错误每次不同的employee_id都会导致一个新的游标被硬解析 String sql “SELECT name FROM employees WHERE id ” empId; // 正确使用绑定变量相同的SQL文本可以共享游标 String sql “SELECT name FROM employees WHERE id ?”; PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1, empId);使用绑定变量无论传入的值如何变化SQL文本保持不变Oracle可以极大地重用共享池中的游标显著减少游标总数和解析开销。踩过的一个坑曾经遇到一个报表系统每天凌晨跑批时必现游标超限。排查后发现开发同事为了“灵活”在循环中动态拼接了上百个不同的查询条件组合生成了成千上万条字面值不同的SQL。将代码重构为使用绑定变量后游标使用量从峰值近千个下降到稳定在几十个问题彻底消失性能还提升了好几倍。这个案例深刻说明很多数据库性能问题根源都在应用代码的设计上。

相关新闻

Outlook附件大小限制修改:注册表原理与一键配置实战

Outlook附件大小限制修改:注册表原理与一键配置实战

1. 项目概述:为什么我们需要修改Outlook附件大小限制?如果你经常用Outlook收发邮件,尤其是处理一些设计稿、视频文件或者项目压缩包,大概率都遇到过那个让人头疼的弹窗:“附件大小超过允许的限制”。这个默认的限制&am…

2026/10/13 5:15:11 阅读更多 →
解决无畏契约 ACE 虚拟化弹窗与 VAL 29 的排查记录

解决无畏契约 ACE 虚拟化弹窗与 VAL 29 的排查记录

最近打《无畏契约》时遇到了一串问题,最开始是 ACE 安全中心弹窗提示:电脑未开启或有其他软件占用 CPU 虚拟化功能,请进入 BIOS 开启 VT-x / VT-d 或 AMD-V,并关闭可能占用 VT 的相关软件或功能。我一开始按照官方教程进 BIOS 检查…

2026/10/13 15:26:32 阅读更多 →
Oracle ORA-01000错误深度解析:从游标泄露到根治方案

Oracle ORA-01000错误深度解析:从游标泄露到根治方案

1. 项目概述:当Oracle开始“罢工”在数据库运维和开发领域,Oracle数据库的稳定性和性能是业务连续性的基石。然而,即便是经验丰富的DBA或开发者,也难免会遇到一些看似棘手、实则根源清晰的报错。其中,“ORA-01000: max…

2026/10/13 14:41:20 阅读更多 →

最新新闻

AI辅助量化策略开发:从零搭建ETF轮动回测框架

AI辅助量化策略开发:从零搭建ETF轮动回测框架

1. 为什么说现在是个人做量化的最好时机我接触量化交易差不多有七八年了,最早那会儿,个人想做一套自己的策略,门槛高得离谱。你得自己搭数据库、自己写回测框架、自己处理除权除息、自己搞行情接口,光是数据清洗就能耗掉大半个月。…

2026/10/14 8:10:50 阅读更多 →
M/o/Vfuscator 入门示例:用单条 mov 指令编译 prime.c,与 GCC 输出逐行对比

M/o/Vfuscator 入门示例:用单条 mov 指令编译 prime.c,与 GCC 输出逐行对比

编译器 【免费下载链接】movfuscator The single instruction C compiler 项目地址: https://gitcode.com/gh_mirrors/mo/movfuscator 点击查看 免费下载 本文以 movfuscator 仓库中的 overview/ 目录为例,展示如何用 M/o/Vfuscator 编译器(…

2026/10/14 8:10:50 阅读更多 →
openrig 装配指南:Node.js、tmux 与 Claude Code/Codex 本地工作台搭建

openrig 装配指南:Node.js、tmux 与 Claude Code/Codex 本地工作台搭建

1. 从 openrig 这个名字说起:它到底想解决什么问题第一次看到openrig这个词,我脑子里蹦出来的不是某个具体工具,而是一种"把散装零件拼成一台整机"的直觉。rig 在英文里本意是"装配、搭建",在工程语境里常指把…

2026/10/14 8:10:50 阅读更多 →
ECCV2020三维视觉技术路线图:点云处理与3D检测的范式跃迁

ECCV2020三维视觉技术路线图:点云处理与3D检测的范式跃迁

1. 这不是论文清单,而是一份三维视觉领域的技术路线图ECCV 2020那届会议我全程跟下来了,当时在苏黎世现场听oral报告时,就明显感觉到——三维视觉的范式正在悄悄转移。过去几年大家还在争论“点云该不该用MLP堆叠”,到了2020年&am…

2026/10/14 8:10:50 阅读更多 →
REA 接入前的技术核对:静态分析、后端和 MCP 分别检查什么

REA 接入前的技术核对:静态分析、后端和 MCP 分别检查什么

REA 接入前的技术核对:静态分析、后端和 MCP 分别检查什么 准备把 REA 接到 Agent 时,先区分三个问题:分析输入是什么,对应工作流需要什么依赖,Agent 是否真的连到了这项能力。它们出错时的表现可能相似,处…

2026/10/14 8:10:50 阅读更多 →
照片隐私保护:EXIF元数据清除工具与技术解析

照片隐私保护:EXIF元数据清除工具与技术解析

1. 为什么我们需要关注照片隐私问题上周帮朋友处理一张手机拍摄的证件照时,无意间用专业软件查看了它的EXIF信息,结果发现里面完整记录了拍摄时间、GPS坐标、手机型号甚至系统版本。这让我意识到——我们每天随手分享的照片,可能正在泄露大量…

2026/10/14 8:09:50 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →