浅谈 ROWID 的组织机制及其在回表路径中的作用

张开发
2026/4/17 23:55:40 15 分钟阅读

分享文章

浅谈 ROWID 的组织机制及其在回表路径中的作用
一、ROWID 与普通表的默认聚集组织达梦普通表列存表和堆表除外采用聚集索引组织方式存储数据。每个表有且仅有一个聚集索引当建表时未显式指定聚集索引键系统默认以ROWID作为聚集索引键表中记录也因此按ROWID的顺序组织。由此可见在普通表中ROWID并不只是一个用于标识记录的伪列它同时还是表记录组织与访问路径的重要基础。与之对应达梦的非聚集索引即二级索引叶子节点中保存的是“二级索引列 聚集索引列”。这意味着二级索引能够直接提供的并不是整行记录而是索引键及其对应的聚集定位依据。当查询仅涉及索引列或聚集列时可以直接从二级索引返回结果而当查询需要访问其他列时则必须依据叶子节点中保存的聚集索引列回到一级索引继续定位整行记录这就是回表。因此在select * from ...一类查询中即使已经通过二级索引命中了目标记录执行计划中仍然通常会出现SSEK2与BLKUP2的组合前者负责完成二级索引扫描后者负责依据聚集定位信息回到一级索引取出完整记录。其根本原因在于二级索引叶子节点并不保存整行数据而只保存索引键与聚集索引列。换言之二级索引叶子中携带的本质上是指向ROWID聚集组织的定位依据BLKUP2的作用也正是沿着这条定位链回到按ROWID组织的聚集结构中取出整行数据。二、ROWID 的作用在达梦数据库中ROWID的作用并不局限于“标识一条记录”。对于普通表而言ROWID同时承担了记录标识、表组织依据和访问定位锚点等多重职责。理解ROWID在数据库里的作用关键不在于把它看成一个可查询的伪列而在于把它放回到表的组织方式和访问路径中去观察。首先ROWID参与普通表的默认聚集组织。达梦普通表在未显式指定聚集键时默认以ROWID作为聚集索引键表中记录按ROWID的顺序组织。因此ROWID不是一个脱离存储结构独立存在的附加属性而是普通表聚集组织本身的一部分。数据库在组织、排序和访问表中记录时天然以ROWID为基础。也正因为如此ROWID在普通表中既表示“这条记录是谁”也参与决定“这条记录在表中如何被组织和访问”。其次ROWID是二级索引回表时的重要定位依据。达梦二级索引叶子节点保存的是“二级索引列 聚集索引列”而在默认普通表中聚集索引列通常就是ROWID。这意味着二级索引命中后返回的并不是整行数据而是索引键及其对应的聚集定位依据。当查询需要访问索引键以外的列时执行器就必须依据这份定位依据回到一级索引继续查找整行记录。换句话说ROWID在这里的作用是把“二级索引中的命中结果”和“表中的完整记录”连接起来使二级索引能够进一步落到真实数据行。再次ROWID还影响访问路径的代价。对于数据库而言ROWID不仅决定能否定位到目标记录也影响定位过程需要经过怎样的路径。默认普通表中的许多访问过程本质上都是围绕ROWID所代表的聚集组织展开的。特别是在二级索引命中但又不能覆盖访问的场景中执行器需要借助ROWID或对应的聚集定位信息完成从索引记录到整行记录的转换。因此ROWID不仅参与访问而且参与决定访问成本。从更广的角度看ROWID还具有记录归属和组织分析的价值。ROWID本身携带了记录在数据库组织体系中的编码信息可以用来表示记录所属的站点、分区以及在表组织中的物理行号。也就是说ROWID不只是访问路径中的中间变量还提供了一种观察记录归属、分布和组织方式的入口。对于理解表的默认聚集方式、分析访问路径以及进一步观察数据库内部组织而言ROWID都具有独立价值。因此ROWID在数据库里的作用可以概括为三层它是记录的系统标识是普通表默认聚集组织的基础也是二级索引回表过程中的定位锚点。后文将结合实验进一步验证这些作用在执行计划和访问代价上的具体体现。三、验证实验一验证 ROWID 在默认普通表与显式 cluster 表中的组织作用A. 默认普通表t_rowid步骤 1创建默认普通表并写入数据执行droptablet_rowid;createtablet_rowid(idint,c1varchar(20),c2varchar(20));insertintot_rowidselectlevel,dbms_random.string(U,20),dbms_random.string(U,20)fromdualconnectbylevel100000;commit;步骤 2查询若干条记录的 ROWID执行setautotraceoff;selectrowid,idfromt_rowidwhereidbetween100and110;记录其中一条ROWID例如AAAAAAAAAAAAAAABl步骤 3开启 autotrace 与执行监控执行setautotrace traceonly;altersessionsetmonitor_sql_exec1;步骤 4按 ROWID 精确查询执行select*fromt_rowidwhererowidAAAAAAAAAAAAAAABl;实验现象执行计划中出现CSEK2并直接对t_rowid的聚集索引进行定位scan_range为同一个ROWID常量该次查询rows processed 1logical reads 2physical reads 0。这说明在默认普通表中ROWID条件被直接转化为了聚集访问路径上的定位条件。B. 显式 cluster 表t_clu步骤 5创建显式 cluster 表并写入数据执行droptablet_clu;createtablet_clu(idintnotnull,c1varchar(20),c2varchar(20),clusterprimarykey(id));setautotraceoff;insertintot_cluselectlevel,dbms_random.string(U,20),dbms_random.string(U,20)fromdualconnectbylevel100000;commit;步骤 6查询若干条记录的 ROWID执行setautotraceoff;selectrowid,idfromt_cluwhereidbetween100and110;记录其中一条ROWID。步骤 7开启 autotrace 与执行监控执行setautotrace traceonly;altersessionsetmonitor_sql_exec1;步骤 8按 ROWID 精确查询执行select*fromt_cluwhererowid记录下来的某个ROWID;实验现象执行计划中未出现按ROWID直接定位的CSEK2而是表现为CSCN2 SLCT2CSCN2负责聚集扫描SLCT2上的谓词为T_CLU.ROWID var1。该次查询rows processed 1logical reads 439physical reads 0。这说明在显式cluster primary key(id)的表中ROWID条件没有被转化为聚集访问键而是作为扫描后的过滤条件存在。C. 整理对照结论同样是where rowid 常量的等值查询默认普通表t_rowid上表现为CSEK2直接定位逻辑读仅为 2显式 cluster 表t_clu上则表现为CSCN2 SLCT2逻辑读上升到 439。这个对照说明在默认普通表t_rowid上ROWID作为默认聚集索引键where rowid 已知量可直接走CSEK2聚集索引定位而在显式指定cluster primary key(id)的t_clu上聚集索引键已变为idROWID虽然仍可标识记录但已经不再是该表的聚集访问键因此执行计划退化为CSCN2聚集索引扫描配合SLCT2过滤。两者的执行计划差异正体现了ROWID在默认表中是聚集组织键、而在显式 cluster 表中只是记录属性而非聚集访问键的本质区别。实验二验证 WITH CLU_REC_ADDR 对回表路径的优化效果步骤 1准备测试表和数据执行droptabletest1;createtabletest1(idint,c1varchar(20),c2varchar(20));truncatetabletest1;insertintotest1selectlevel,dbms_random.string(U,20),dbms_random.string(U,20)fromdualconnectbylevel100000;commit;步骤 2创建带地址信息的二级索引执行createorreplaceindexidx_dmtest1_c1ontest1(c1)withclu_rec_addr;步骤 3开启执行监控与 autotrace执行setautotrace traceonly;altersessionsetmonitor_sql_exec1;步骤 4执行查询执行select/*plan_op_flag(8)*/*fromtest1wherec10;步骤 5重建成普通索引执行createorreplaceindexidx_dmtest1_c1ontest1(c1);步骤 6再次执行同一条 SQL执行select*fromtest1wherec10;步骤 7整理对照结论两次执行逻辑读的次数相差了两个数量级。这说明WITH CLU_REC_ADDR 带来的收益并不只是少一次“通过逻辑 ROWID 再定位聚集记录”的查找。因为如果收益仅来自这一步的消除那么访问代价虽然会因减少定位聚集索引内记录而下降但难以单独解释当前测例中两个数量级的差异。更合理的解释是二级索引在额外带上聚集记录地址/页地址后执行器在回表阶段不再是逐条按聚集键重复定位记录能够按更有利于物理页访问的方式组织目标记录访问使同页或相邻页上的记录更集中地完成读取从而显著降低回表阶段的逻辑读。在DM里物理 ROWID 的形式是通过文件号、页号和页内偏移直接生成该值不需要关联逻辑 ROWID 值。倘若二级索引外带上聚集记录地址/页地址当通过二级索引获得大量物理 ROWID 时可以不经过排序地合并页号相同的记录从而读一页可以获得多条记录不需要每一条记录都单独进行一次寻址操作让BLKUP2 阶段的定位与访问成本进一步大幅下降。通过这种方式可以把降低逻辑读的比例从三分之一再下探到两个数量级。四、结论这是一种“回表操作符级优化”不是“计划消除型优化”《DM8 系统管理员手册管理索引》里面写道二级索引叶子节点保存的是二级索引列和聚集索引列查询其他列时必须回聚集索引查找BLKUP2正是这个过程的执行体现。WITH CLU_REC_ADDR对应的use_clu_addr能力优化的就是“从二级索引记录定位聚集记录”的过程。因此这项能力的本质不是“把BLKUP2去掉”而是“让BLKUP2更便宜”。它最适合解决“回表无法消除但成本太大”的场景《DM8 系统管理员手册SQL 调优》里提到可以通过覆盖索引直接避免BLKUP2的二次 IO但覆盖索引会显著增大索引体积需要权衡。WITH CLU_REC_ADDR则适用于另一类场景SQL 结果列多、业务查询复杂、无法轻易改成覆盖索引但又必须控制回表逻辑读。这类场景正是它的价值所在。从机制上看这项能力更像是在现有表组织方式不变的前提下对默认回表路径做减法。表仍然按原有聚集方式组织二级索引仍然负责按业务键命中记录BLKUP2也仍然存在真正变化的是二级索引记录中额外保留了更利于定位聚集记录的地址信息使得执行器在回表阶段可以减少重复定位带来的逻辑读和访问代价。因此它的价值不在于“换了一种计划”而在于即使计划主形态不变仍然能够通过缩短回表链路获得收益。五、ROWID 的扩展用途与分析价值在达梦数据库中ROWID并不是一个仅用于语法层访问的伪列也不是一个可以简单等同为“当前物理地址”的内部编号。它同时承担了记录标识、表组织、访问路径定位与分析入口等多重角色。理解ROWID需要将“记录身份”“聚集组织”“回表定位”以及“物理分布观察”几个层面放在同一框架内考察。ROWID 与 PHYROWID 的区别讨论ROWID时需要先区分ROWID与PHYROWID。ROWID公开表达的是站点号、分区号和物理行号这套编码信息PHYROWID则用于表示当前记录的物理存储信息其内容由物理记录的文件号、页号和页内槽号组成。官方进一步指出PHYROWID的含义还与实际访问路径相关当语句执行中使用CSCN、CSEK、BLKUP等聚集 B 树相关操作符时PHYROWID反映的是聚集 B 树中的物理地址当语句仅使用SSEK、SSCN等二级索引相关操作符时PHYROWID反映的是二级 B 树中的物理地址。这一划分意味着ROWID更偏向于“记录在表组织体系中的身份坐标”而PHYROWID更偏向于“当前访问路径下记录具体落在哪个文件、哪一页、哪一个槽位”。如果不区分这两个层次就容易把行标识、聚集组织键与当前物理页地址混为一谈从而在后续讨论回表、页分布或空间膨胀时发生概念错位。值得注意的是达梦堆表采用的是PHYROWID。官方文档明确说明堆表的PHYROWID由文件号、页号和页内偏移直接生成因此一旦知道堆表记录的ROWID就可以直接定位到该条记录的物理落点。ROWID 携带的信息从编码结构看ROWID将一条记录的定位信息拆分成了三个维度站点号对应记录所属站点分区号对应记录所属分区物理行号对应记录在表组织中的物理行编号。达梦 SQL 附录提供了一组与ROWID直接相关的系统函数例如SF_BUILD_ROWID用于根据站点号、分区号和物理行号构造ROWIDSF_ROWID_GET_EP_SEQNO用于获取站点号SF_ROWID_GET_PARTNO用于获取分区号SF_GET_REAL_ROWID用于获取物理行号。由此可见ROWID并不是一个不可解释的黑箱值而是一种可构造、可拆解、可分析的系统标识。这种编码方式的意义在于它把“记录是谁”“记录属于哪里”“记录在当前组织中位于什么位置”转化为可以被程序和运维工具直接读取的信息。在单机环境下站点号通常为零值即AAAA如果是非分区表则 6 位分区号为0即AAAAAA。在分区表、DSC、MPP 或 DPC 等更复杂的环境中这些字段则直接承担了记录来源与归属的表达功能。对于定位问题、分区归属验证、跨站点排查以及系统级数据诊断而言这类信息具有直接价值。ROWID 的用途ROWID的最直接用途是对已经找到的记录进行精确重访。官方文档里给出的典型模式是查询时同时取出ROWID后续更新语句再通过ROWID精确访问目标行。若查询时使用FOR UPDATE则该行会被锁定从而保证查询和后续更新之间的一致性。这一能力适用于精确修数、无业务主键表的单行更新以及“先查后改”的稳定定位场景。ROWID的第二类用途是支撑表访问路径中的“回表”机制。达梦官方索引文档指出非聚集索引二级索引的叶子节点共同存储“二级索引列 聚集索引列”如果查询只涉及二级索引列或聚集索引列索引层即可直接返回如果查询还需要其他列则必须回到一级索引继续查找。由于普通表默认聚集索引键通常为ROWID因此二级索引命中后返回聚集索引进一步取整行本质上就是围绕ROWID展开的定位过程。进一步地ROWID还进入了优化器的代价模型。SQL查询优化文档明确指出二级索引扫描的代价不仅依赖于 B 树层次和需扫描的叶子块数还依赖于“根据ROWID访问聚集索引的记录数”。这意味着ROWID在执行层不仅是回表的技术细节而且直接参与了访问代价的估算。在执行计划说明中BLKUP2的use_clu_addr属性表示“是否从二级索引记录读取对应的聚集索引记录地址对定位聚集索引记录进行优化”从而进一步说明了ROWID及其相关地址信息在回表优化中的核心地位。ROWID的第三类用途是在无主键或不依赖主键的场景中承担增量定位角色。达梦物化视图文档表明快速刷新既可以基于主键也可以基于ROWID对于WITH ROWID的快速刷新单表场景下物化视图日志必须包含ROWID多表场景下每张基表的日志都必须包含ROWID并且查询定义中需要逐一选择ROWID并给出别名。这说明ROWID不只是单表内部的访问信息还可以成为增量刷新与数据同步机制中的定位。ROWID的第四类用途是在日志挖掘与变更重构中标识目标行。达梦DBMS_LOGMNR文档显示NO_ROWID_IN_STMT选项用于控制拼写 SQL 时是否包含ROWID。这意味着在日志挖掘默认语境下ROWID是重构 DML 目标行的重要线索之一也正因此关闭ROWID输出需要被作为显式选项处理。对于审计、恢复、差异分析与日志级排障而言这一能力具有明显意义。ROWID的第五类用途则体现在更底层的物理分布分析上。这里主角已经不再是抽象的ROWID编码而是ROWID体系中的PHYROWID。借助PHYROWID中的文件号、页号和槽号可以把“某条记录位于哪一页”这一事实显式化再结合SF_GET_ROWID_BY_PAGE(ts_id, file_id, page_no)这类页级函数可以从页反向获取记录集合从而开展页分布、热点页、稀疏度、回表离散度、碎片化与空间膨胀等分析。一组 ROWID 包含的信息1. 可以统计站点分布、分区分布与逻辑归属分布达梦公开给出的ROWID结构是“站点号 分区号 物理行号”并提供了SF_ROWID_GET_EP_SEQNO、SF_ROWID_GET_PARTNO、SF_GET_REAL_ROWID等函数用于拆解。因此多个ROWID放在一起最直接可以做的是统计记录来自哪些站点、属于哪些分区、各分区占比如何、某类业务数据是否集中在特定分区。这类统计并不反映页级物理位置但非常适合做逻辑归属分析、分区落点验证和分布式环境下的数据来源分析。2. 可以分析记录在普通表组织中的顺序特征达梦普通表在未显式指定聚集索引键时默认聚集索引键是ROWID官方文档同时说明在这种组织方式下ROWID是 B 树为记录生成的逻辑递增序号。这意味着在普通表语境下多个ROWID可以用来观察记录在聚集组织中的先后关系、范围分布以及某些批次数据是否集中在相近的ROWID区间。3. 可以验证二级索引回表的逻辑归并程度达梦二级索引叶子节点存储的是“二级索引列 聚集索引列”若查询列不在二级索引中就需要再根据ROWID或聚集索引定位到数据行。由此可知多个ROWID还可以用来分析某次查询命中的结果集在聚集组织上是否集中若命中的ROWID范围较连续通常说明回表时更容易具备局部性若ROWID分布高度离散则意味着命中的逻辑落点更分散。它提供了一个重要视角查询命中的记录在表的逻辑组织层面是集中还是分散。4. 可以为无主键场景提供增量定位与集合比对依据达梦支持基于ROWID的物化视图与快速刷新官方要求在WITH ROWID的快速刷新场景中查询定义中必须显式选择所有相关ROWID列并给出别名。这说明多个ROWID还可以用作集合级比较与增量定位的键例如在无主键表中用一组ROWID表示“这一批被追踪的记录”或者在刷新与同步过程中用ROWID作为变化集合的定位依据。因此多个ROWID可以做站点/分区分布统计、逻辑区间分布分析、回表逻辑集中度分析以及无主键场景下的集合定位。一组 PHYROWID 包含的信息1. 可以统计页分布与页内记录数分布这是最直接、也是最可靠的用途。因为PHYROWID公开对应文件号、页号、页内槽号所以把多个PHYROWID放在一起至少可以把记录按“文件号 页号”归并得到“哪些记录落在同一页”。一旦完成这种归并就可以继续统计每页记录数的分布例如平均每页多少行、中位数多少行、众数是多少行、最稀疏页与最密集页分别是什么状态也就是页利用状态的分布。这个结论不依赖对页大小的反推而是直接建立在PHYROWID的定位语义上。2. 可以分析页离散度、聚簇性与访问局部性当多个PHYROWID与某种业务顺序结合起来时例如按主键顺序、按插入顺序或按某个查询结果顺序观察就可以进一步分析记录在物理页上的连续性。若相邻业务记录对应的PHYROWID经常落在相邻页或同一页说明物理局部性较好若这些记录频繁跳页则说明物理离散度较高。由于达梦索引扫描和回表都与“根据ROWID或聚集索引继续找到数据行”有关页离散度越高通常意味着逻辑读与物理读的局部性越差。3. 可以在已知页大小后进一步估算页填充率与膨胀率PHYROWID自身不能推出页大小页大小必须从实例参数单独获取例如select page/1024;或查询GLOBAL_PAGE_SIZE。但是一旦页大小已知多个PHYROWID就可以与对象实际占用页数结合起来进一步估算页填充率、页利用率与膨胀率。第一步依据PHYROWID将记录按页归并得到每页记录数分布。第二步依据对象空间统计函数获取表或索引实际占用页数。第三步将“理论每页可容纳记录数”与“实际每页记录数分布”对照或将现状对象与重建后的紧凑对象对照由此求出页利用率下降幅度与膨胀倍数。这里的“理论容量”来自页大小与行长模型而不是来自PHYROWID本身。4. 可以做页级反查与页热点定位达梦还提供了按页反查ROWID的系统函数SF_GET_ROWID_BY_PAGE(ts_id, file_id, page_no)。这说明页并不是一个只能通过底层工具观察的概念而是可以通过ROWID体系进行程序化分析。若配合多个PHYROWID的分布统计就可以定位出记录集中分布的热点页、过于稀疏的低效页以及某一类业务数据主要落在哪些文件与页区间。

更多文章