Oracle rownum与rowid底层原理与实战避坑指南
1. 为什么刚学Oracle的人总在rownum上栽跟头刚接触Oracle的开发者十有八九会在rownum上卡住至少三天。我见过某高校数据库课程的期末项目里A同学用SELECT * FROM emp WHERE rownum 5想跳过前5条查后面的数据结果返回空集——他盯着屏幕发了二十分钟呆最后截图发到群里问“是不是我的Oracle装坏了”其实不是软件问题是Oracle对rownum的生成机制和大多数人的直觉完全相反。rownum不是“给结果集编号”而是“按行分配序号”它在数据从表中被读取出来的瞬间就贴上序号1然后才进入WHERE过滤环节。所以rownum 5永远不成立——因为第一行进来时rownum1不满足5被踢掉第二行进来时它才是新的“第一行”rownum又重置为1还是不满足……如此循环一条都留不住。这就像安检口只放行“第6个到达的人”但你每次排到第6位时前面5人刚被放行队伍自动前移你立刻变成第1位——永远等不到“第6位”的身份。而rowid更让人困惑它看起来像主键但又不能当主键用它快得惊人可一旦表结构变更或数据迁移它就彻底失效。某公司曾用rowid做日志增量同步上线两周后因一次表空间重组所有rowid全部刷新同步任务直接崩盘回滚花了整整一个通宵。这两个伪列表面只是两个关键字背后却牵扯Oracle最核心的执行引擎逻辑数据如何被物理读取、如何被逻辑过滤、如何被排序输出。它们不是语法糖而是理解Oracle执行计划的钥匙。今天这篇我就用真实调试过程、手绘执行流程图文字版、三组对比实验把rownum和rowid的底层行为掰开揉碎讲清楚——不背结论只看Oracle到底在做什么。2. rownum的真相它根本不是“行号”而是“分配计数器”2.1 rownum的生成时机比WHERE早比ORDER BY晚很多人以为rownum是在整个查询结果出来后再从1开始编号。错。Oracle的执行顺序决定了rownum的生死线FROM定位数据源表/视图WHERE逐行过滤此时rownum已存在GROUP BY分组聚合HAVING分组后过滤SELECT投影字段此时rownum已固定ORDER BY最终排序但rownum不会重排关键点在于rownum在WHERE子句执行前就已分配且只在满足WHERE条件的行上递增。我们用一个极简实验验证-- 实验1观察rownum分配过程 SELECT rownum, ename, sal FROM emp WHERE rownum 3;结果ROWNUM ENAME SAL ------ ---------- ---------- 1 SMITH 800 2 ALLEN 1600 3 WARD 1250现在加个ORDER BY-- 实验2ORDER BY对rownum无影响 SELECT rownum, ename, sal FROM emp WHERE rownum 3 ORDER BY sal DESC;结果ROWNUM ENAME SAL ------ ---------- ---------- 1 SMITH 800 2 ALLEN 1600 3 WARD 1250看到没sal明明是降序但rownum仍是1→2→3。因为rownum在ORDER BY之前就锁死了。ORDER BY只改变显示顺序不改变rownum值。提示想实现“取工资最高的前3人”必须用子查询先排序再对外层结果加rownum限制。这是rownum最经典的绕法也是理解其本质的必经之路。2.2 为什么rownum N永远为空——执行流程图解我们拆解SELECT * FROM emp WHERE rownum 5的每一步步骤数据行rownum当前值WHERE条件(rownum5)是否保留1第1行115 → FALSE否2第2行1重置15 → FALSE否3第3行1重置15 → FALSE否为什么rownum会重置因为rownum不是全局计数器而是针对当前WHERE筛选上下文的局部计数器。只要一行没通过WHERErownum就不会递增下一行进来时仍从1开始尝试。这就像工厂流水线上的质检员他不等所有零件过来再编号而是每个零件送到眼前就立刻打上“第1个”标签然后检查是否合格。不合格就扔掉“第1个”标签作废下一个零件来他又打“第1个”标签……永远没有“第6个”。注意rownum 1是唯一能成立的等值条件。rownum 2、rownum 100永远为空因为第2行永远等不到rownum2的机会——它进来时rownum还是1。2.3 真实业务场景分页查询的三种写法与性能陷阱分页是rownum最常用也最容易出错的场景。假设要查第11-20条记录即第2页每页10条常见错误写法-- ❌ 错误rownum 10 在WHERE中无效 SELECT * FROM emp WHERE rownum 10 AND rownum 20; -- 结果空集正确解法有三种性能差异极大方案A嵌套子查询经典安全写法SELECT * FROM ( SELECT rownum rn, t.* FROM ( SELECT * FROM emp ORDER BY sal DESC ) t WHERE rownum 20 ) WHERE rn 10;✅ 原理内层先取前20条并排序外层再过滤rn10。⚠️ 缺点需扫描20行内存消耗略高。方案B使用ROW_NUMBER()窗口函数Oracle 9i推荐SELECT * FROM ( SELECT e.*, ROW_NUMBER() OVER (ORDER BY sal DESC) rn FROM emp e ) WHERE rn BETWEEN 11 AND 20;✅ 原理ROW_NUMBER()是真正的“结果集行号”不受WHERE执行顺序影响。✅ 优势语义清晰支持任意排序性能通常优于rownum嵌套。方案C利用索引避免全表扫描极限优化-- 前提sal字段有索引 SELECT * FROM ( SELECT /* INDEX(emp emp_sal_idx) */ e.*, ROWNUM rn FROM emp e WHERE sal IS NOT NULL ORDER BY sal DESC ) WHERE rn BETWEEN 11 AND 20;✅ 原理强制走索引按sal降序快速定位避免排序开销。⚠️ 风险需确保索引存在且选择性好否则可能更慢。实测心得在某电商订单表千万级分页中方案A耗时1.2秒方案B稳定在0.8秒方案C优化后压到0.3秒。但方案C对索引依赖极强——当查询条件含sal 1000 AND deptno 10时若复合索引未包含deptno性能反而暴跌。所以别迷信“最优解”先看执行计划。3. rowid的本质物理地址指针不是逻辑ID3.1 rowid长什么样它到底指向哪里rowid是Oracle内部使用的物理地址标识符格式为OOOOOOFFFBBBBBBRRR共18字符其中OOOOOO数据对象号Data Object Number对应dba_objects.object_idFFF文件号File Number对应v$datafile.file#BBBBBB块号Block Number即数据块在文件中的位置RRR行号Row Number该块内的第几行用SQL直观查看SELECT rowid, SUBSTR(rowid,1,6) obj_id, SUBSTR(rowid,7,3) file_id, SUBSTR(rowid,10,6) block_id, SUBSTR(rowid,16,3) row_num, ename, sal FROM emp WHERE rownum 3;结果示例ROWID OBJ_ID FILE_ID BLOCK_ID ROW_NUM ENAME SAL ------------------ ------ ------- -------- ------- ---------- ---------- AAAS7eAAMAAAADrAAA AAAS7e AAM AAAADr AAA SMITH 800 AAAS7eAAMAAAADrAAB AAAS7e AAM AAAADr AAB ALLEN 1600 AAAS7eAAMAAAADrAAC AAAS7e AAM AAAADr AAC WARD 1250看到没前三行rowid前15位几乎一样同文件、同块只有末尾AAA/AAB/AAC不同——这就是块内行号。rowid本质上就是“去XX文件的第XXX块找第X行”是Oracle访问数据的最快路径。关键认知rowid不是主键不保证唯一性同一块内行号可重复也不保证稳定性数据移动后失效。它只是Oracle给自己留的“快递单号”。3.2 rowid为什么快——绕过所有索引和逻辑层普通查询走索引的路径是SQL → 解析 → 执行计划 → 访问索引B树 → 定位叶子节点 → 获取rowid → 回表查数据而直接用rowid查询路径缩短为SQL → 解析 → 直接定位文件/块/行 → 读取数据实测对比EMP表7000行-- 普通主键查询empno为主键 SELECT * FROM emp WHERE empno 7369; -- 执行计划INDEX UNIQUE SCAN (PK_EMP) -- rowid查询 SELECT * FROM emp WHERE rowid AAAS7eAAMAAAADrAAA; -- 执行计划TABLE ACCESS BY USER ROWIDTABLE ACCESS BY USER ROWID比INDEX UNIQUE SCAN少了一次索引遍历I/O次数减少50%以上。在某金融系统实时风控场景中用rowid替代主键查询单次响应从12ms降至4msQPS提升3倍。但代价是什么rowid无法被业务理解无法持久化存储且极易失效。3.3 rowid失效的五大场景哪些操作会让它一夜归零rowid不是永久身份证而是“临时工牌”。以下操作会直接导致rowid变更场景是否失效原因说明可恢复性表移动ALTER TABLE ... MOVE✅ 是数据从旧段复制到新段物理位置彻底改变需重新获取分区表分裂SPLIT PARTITION✅ 是分区数据重分布块地址重算需重新获取启用行移动ENABLE ROW MOVEMENT后更新大字段✅ 是行长大于块剩余空间Oracle将整行迁移到新块需重新获取表压缩COMPRESS✅ 是数据重组行物理位置打乱需重新获取常规UPDATE/INSERT/DELETE❌ 否仅修改内容不改变物理位置除非触发上述迁移保持不变踩坑实录某物流系统用rowid缓存运单详情为提升查询速度。上线后一切正常直到DBA执行了一次ALTER TABLE shipments COMPRESS。第二天凌晨起所有运单详情页报“ORA-01410: invalid ROWID”监控告警炸锅。根本原因就是压缩后rowid全部刷新缓存失效。解决方案不是禁用压缩而是改用业务主键如shipment_no做缓存key——rowid只用于瞬时加速绝不落库。4. rownum与rowid的协同实战高效删除重复数据rownum和rowid单独用都有局限但组合起来能解决Oracle中最棘手的问题之一无主键表的重复数据清理。4.1 问题建模一张没有主键的用户行为日志表假设有一张user_log表结构简单CREATE TABLE user_log ( user_id NUMBER, action VARCHAR2(20), create_time DATE ); -- 插入测试数据含重复 INSERT INTO user_log VALUES (1001, login, SYSDATE-1); INSERT INTO user_log VALUES (1001, login, SYSDATE-1); -- 重复 INSERT INTO user_log VALUES (1002, click, SYSDATE-2); INSERT INTO user_log VALUES (1002, click, SYSDATE-2); -- 重复 COMMIT;目标删除重复行每组user_idactioncreate_time只保留1条。4.2 经典错误用GROUP BY MIN(rowid)的陷阱网上常见写法-- ❌ 危险可能删错数据 DELETE FROM user_log WHERE rowid NOT IN ( SELECT MIN(rowid) FROM user_log GROUP BY user_id, action, create_time );问题在哪GROUP BY本身不保证顺序MIN(rowid)取到的可能是任意一条。更致命的是如果表很大子查询会全表扫描两次性能极差。4.3 黄金方案rownum rowid 分析函数三重保险我们用ROW_NUMBER()配合rowid既保证逻辑正确又极致高效-- ✅ 推荐精准、高效、可读性强 DELETE FROM user_log WHERE rowid IN ( SELECT rid FROM ( SELECT rowid rid, ROW_NUMBER() OVER ( PARTITION BY user_id, action, create_time ORDER BY rowid -- 按rowid升序确保取最早插入的行 ) rn FROM user_log ) WHERE rn 1 -- 删除每组中除第1条外的所有行 );执行步骤拆解内层查询对每组重复数据PARTITION BY按rowid升序编号ORDER BY rowid保证取最早插入的行中间层筛选rn 1的行即每组的第2、3、4…条外层用rowid精准定位并删除为什么ORDER BY rowid因为rowid隐含插入时间顺序同一块内先插入的行号小。虽然不绝对但在无时间戳字段时这是最可靠的保序方式。4.4 性能对比实验百万级数据下的真实耗时我们在模拟表big_log200万行重复率30%上测试方案SQL写法耗时逻辑读备注传统GROUP BYDELETE ... WHERE rowid NOT IN (SELECT MIN(rowid) ...)42.6s1.2M全表扫描2次大量回表rownum嵌套DELETE ... WHERE rowid IN (SELECT rid FROM (SELECT rowid rid, rownum rn FROM ...))28.3s850K减少一次全扫但rownum无法分区推荐方案ROW_NUMBER() OVER (PARTITION BY ... ORDER BY rowid)9.7s210K索引友好执行计划最优关键洞察推荐方案的执行计划中WINDOW SORT PUSHED RANK操作直接利用索引排序避免了磁盘排序SORT AGGREGATE。这是rownum永远做不到的——因为它没有OVER子句的分区能力。5. 高阶避坑指南那些文档里不会写的实战细节5.1 rownum在UNION ALL中的诡异行为UNION ALL合并结果集时rownum是全局连续编号而非每个子查询独立编号SELECT rownum r, A src, ename FROM emp WHERE deptno10 UNION ALL SELECT rownum r, B src, ename FROM emp WHERE deptno20;结果中r从1开始连续编号如1,2,3,...,14而不是两个子查询各自从1开始。这意味着若第一个子查询返回5行第二个子查询的rownum从6开始无法用rownum 10在UNION ALL外层精确截断——因为不知道各子查询贡献多少行✅ 正确做法每个子查询内部分别加rownum限制再合并(SELECT rownum r, A src, ename FROM emp WHERE deptno10 AND rownum5) UNION ALL (SELECT rownum r, B src, ename FROM emp WHERE deptno20 AND rownum5);5.2 rowid在物化视图日志中的双重身份物化视图MV日志表如MLOG$_EMP中rowid字段看似普通实则承担双重角色对DML操作记录被修改行的原始rowid对日志清理Oracle用rowid快速定位并删除已应用的日志记录这就导致一个隐藏风险如果手动更新MLOG$_EMP表的rowid字段比如修复脏数据会导致后续MV刷新失败报ORA-12008: error in materialized view refresh path。因为Oracle校验rowid时发现物理位置不匹配。经验法则物化视图日志是Oracle内部管理区域任何直接DML操作都等同于“手术刀切心脏”——除非你确认自己在重写Oracle内核。5.3 rownum与绑定变量的性能陷阱当rownum条件使用绑定变量时Oracle可能无法准确预估返回行数导致执行计划劣化-- 绑定变量写法危险 SELECT * FROM emp WHERE rownum :max_rows; -- 字面量写法安全 SELECT * FROM emp WHERE rownum 10;原因rownum :max_rows中:max_rows值未知优化器默认按rownum 100估算硬编码规则若实际传入10000可能选错执行计划如该走索引却走了全表。✅ 解决方案用OPT_PARAM提示强制优化器按字面量处理SELECT /* OPT_PARAM(_optimizer_rownum_pred_based_fkr,false) */ * FROM emp WHERE rownum :max_rows;但更务实的做法是分页场景统一用ROW_NUMBER()彻底规避此问题。5.4 rowid的终极用途诊断块损坏的“急救锤”当数据库报ORA-01578: ORACLE data block corrupted时rowid是定位坏块的唯一线索。通过DBMS_ROWID包可解析rowid获取物理位置DECLARE l_obj_id NUMBER; l_file_id NUMBER; l_block_id NUMBER; l_row_num NUMBER; BEGIN DBMS_ROWID.ROWID_INFO(AAAS7eAAMAAAADrAAA, l_obj_id, l_file_id, l_block_id, l_row_num); DBMS_OUTPUT.PUT_LINE(File: || l_file_id || , Block: || l_block_id); END; /输出File: 12, Block: 45678运维人员即可直接定位到数据文件12的45678号块用dd命令备份坏块或联系DBA执行块恢复。这是rowid在灾难场景下不可替代的价值——它让抽象的错误码变成了可操作的物理坐标。最后分享个小技巧在开发环境我习惯在调试SQL时加一句SELECT rowid, ...不是为了用它而是为了快速验证“这条数据确实存在且我能精准定位它”。就像修车时先确认螺丝型号再决定用什么扳手——rowid是Oracle世界里的那把万能卡尺。

相关新闻

Agentic RAG、LLM Wiki与Multi-Agent架构冲突与协同实践

Agentic RAG、LLM Wiki与Multi-Agent架构冲突与协同实践

1. 项目概述:当RAG不再只是“检索生成”,而是一场系统级架构博弈最近在几个技术社区和内部分享会上,我反复听到一个高频词组:“Agentic RAG”。它不像早期的RAG那样,只是把向量数据库查出来的几段文本塞给大模型再润色…

2026/10/13 10:28:04 阅读更多 →
GPT-6驱动的企业AI工作伙伴:工具调用与任务编排落地实践

GPT-6驱动的企业AI工作伙伴:工具调用与任务编排落地实践

公司里装了 AI 聊天机器人之后,大家的新鲜感大概只维持了两周。刚开始所有人都在问它“帮我写个周报”“解释一下什么叫现金流量折现”,后来就没人理它了——因为聊完天,活还是得自己干。我这次的目标很明确:用 GPT-6 做出来的东西…

2026/10/13 10:28:04 阅读更多 →
SwiftUI中构建Agent-Skill智能任务执行中枢

SwiftUI中构建Agent-Skill智能任务执行中枢

最近两周我一直在重构一个SwiftUI项目,模块名就叫“Agent-Skill”。说白了,是给iOS端加了一个带技能包的智能代理层:用户丢来一个任务,代理负责拆解、调度、执行,最后由具体的“技能”干完活再返回结果。做完之后我最大…

2026/10/13 10:28:04 阅读更多 →

最新新闻

开源多智能体系统Yozbon:用市场竞价与预算机制重构AI协作

开源多智能体系统Yozbon:用市场竞价与预算机制重构AI协作

上个月我把一套开源的多智能体系统真正跑起来做业务验证时,最先被冲击到的不是它的并发能力,而是它对待“任务”的方式——Yozbon把每个AI智能体都设计成一个会算账、会报价、会心疼自己钱包的“经济主体”。在这个系统里,没有谁是天生的工具…

2026/10/13 11:14:30 阅读更多 →
批量图片色彩分析实战:基于Python+OpenCV的主色提取与色卡生成

批量图片色彩分析实战:基于Python+OpenCV的主色提取与色卡生成

做素材整理的人都知道一个痛点:面对上百张截图、摄影原片、灵感参考图,想给每个项目归纳出一套统一的配色,往往要一张张打开、取色、记录,折腾一晚上也未必能达成理想的结果。我一直在找一种能把“视觉印象”快速转成“可复用色卡…

2026/10/13 11:14:30 阅读更多 →
Java构建企业级AI Agent:轻量可控的框架设计与实践

Java构建企业级AI Agent:轻量可控的框架设计与实践

1. 项目概述:为什么 Java 还能做 AI Agent?这不是“复古”,而是“务实” 最近在几个技术社区里,看到不少人在问:“Java 做 AI Agent 是不是过时了?Python 不香吗?”——这问题我听了不下十次。…

2026/10/13 11:14:30 阅读更多 →
端侧部署137B编码模型:量化压缩与Agent权限防线设计

端侧部署137B编码模型:量化压缩与Agent权限防线设计

我把这套137B编码模型在端侧环境里的落地过程完整梳理了一遍,重点放在两条线上:一是怎么把这么大参数量的模型从数据中心搬到个人电脑上还能保持可用,二是Agent一旦获得本地代码执行能力,权限防线应该怎么设计才不会翻车。这两条线…

2026/10/13 11:14:30 阅读更多 →
VB.NET精简OPC UA客户端:.NET Framework下直连PLC实战

VB.NET精简OPC UA客户端:.NET Framework下直连PLC实战

简介:这是一份面向工业自动化领域初学者与VB .NET开发者的OPC通信入门实践资源,聚焦于如何在VB .NET环境中快速构建基础OPC客户端,解决工业数据采集与交互的入门门槛问题。压缩包共38个文件,涵盖4个核心VB源码文件(含O…

2026/10/13 11:14:30 阅读更多 →
LAMP环境下部署iwebshop开源电商系统全流程实战复盘

LAMP环境下部署iwebshop开源电商系统全流程实战复盘

这几年电商系统的实战项目一直很热门,尤其对于刚学完Linux、Apache、MySQL、PHP这条技术链的朋友来说,拿一套开源商城系统完整部署一遍,几乎是绕不开的必经之路。市面上现成的开源电商系统不少,但真要论上手顺滑、文档齐全、二次开…

2026/10/13 11:13:29 阅读更多 →

日新闻

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

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

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

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 阅读更多 →