【Oracle篇】伪列之ROWID:行数据的物理地址(基于物理地址对行数据最快速度的查询、更新、删除)(第四篇,总共六篇)
💫《博主主页》:
🔎 CSDN主页__奈斯DB
🔎 IF Club社区主页__奈斯、
🔥《擅长领域》:擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控;并对SQLserver、NoSQL(Redis)有了解
💖如果觉得文章对你有所帮助,欢迎点赞收藏加关注💖
想必各位大佬在Oracle运维中会碰到这样一个场景,业务表中存在两条完全重复的行数据👥,需要删除其一、保留另一行。倘若直接执行基于业务条件的 DELETE 语句,那么会导致所有匹配行均被删除 🎯,无法满足需求。
有了解的小伙伴应该知道可以利用 ROWNUM 伪列来限制删除的条数,例如执行 DELETE FROM 表名 WHERE 条件 AND ROWNUM <= 1; ,这种方法虽简单有效 ✅,但存在局限行:其删除结果具有不确定性 🎲,无法精确控制具体删除哪一行,仅适用于对删除目标无严格要求的场景。
除了上述利用 ROWNUM 方式之外,更为可靠的方法是借助Oracle为每一行数据隐式分配的物理地址标识——ROWID 🏷️。在Oracle中每个 ROWID 是一行数据的唯一标识,相当于数据行的物理坐标🗺️。删除重复行数据操作时,首先通过查询语句 SELECT t.*, ROWID FROM 表名 t WHERE 条件; 确定目标数据行的具体 ROWID 值 🔍,随后执行 DELETE FROM 表名 WHERE ROWID = ‘具体值’; 即可实现精准删除 ✂️。
当然 ROWID 的用途远不止于精准删除⚡,它也是实现Oracle最高效数据访问的终极路径 🚀,基于 ROWID 的查询 (如SELECT * FROM 表名 WHERE ROWID = ‘…’) 可直接定位到数据块,跳过索引遍历,仅需一次I/O操作即可返回结果⚡(关于通过索引查找时进行的两次I/O检索在下面会详细介绍)
,其性能远超任何索引方式 🏆。此外,ROWID 在单行更新、分析行迁移与行链接等底层存储管理中同样发挥着至关重要的作用 🔧。滴滴!那么上车出发🚗💨,下面开始 ROWID 伪列的介绍。
在Oracle 12c官方文档中可查的的伪列总共有 10个 ,对于网上的博客,以及其他学习资料对伪列的介绍都非常少,几乎没有大佬去讲关于伪列的内容,那么博主将用一个系列去认真介绍一下这 10个 伪列。先介绍一下伪列,伪列也叫虚拟列、特殊列、隐式列,不管怎么称呼,都泛指伪列。伪列是Oracle中一种特殊的列,它不像普通列那样存储在表中,但可像普通列一样在查询中进行引用,在查询时由数据库动态生成,主要用于获取行标识、序列值、数据操作相关元数据等特殊信息。需要注意的是伪列只能进行查询,不能插入、更新或删除它们的值。伪列类似于无参数的函数,但无参数函数通常对结果集中的每一行返回相同值,而伪列通常为每一行返回不同的值。
对于比较常用的伪列将用独立的一篇文章介绍,对于不常用的伪列将用一篇文章简单介绍清楚其用途即可,因为在实际工作中有几个伪列使用的很频繁,真的非常有用处,关于伪列的系列文章如下:
- 第一篇:伪列之ORA_ROWSCN:精准查看行数据被修改的最后时间
- 第二篇:伪列之Version Query:全链路追踪行数据变更的所有记录(不仅仅是被修改的最后时间)
- 第三篇:伪列之Sequence:利用CURRVAL和NEXTVAL实现主键自增(含 12c 新特性通过 Identity 列实现主键自增)
- 第四篇:伪列之ROWID:行数据的物理地址(基于物理地址对行数据最快速度的查询、更新、删除)(当前篇)
- 第五篇:伪列之ROWNUM:分页查询的实现
- 第六篇:伪列之Hierarchical Query(层次查询)、COLUMN_VALUE(列值提取)、OBJECT_ID(对象标识)、OBJECT_VALUE(对象值访问)、XMLDATA(XML原始数据)
特别说明💥:本篇文章部分理论性知识点均来源于版权归 Oracle 所有的官方公开文档手册,并结合了我个人的解读和案例演示。若需要调整,请联系,会尽快处理😄
官方文档对于ROWID伪列的介绍(Oracle 12c):
ROWID Pseudocolumn
官方文档对于数据块格式,以及ROWID格式的介绍(Oracle 12c):
Data Block Format
目录
- 一、ROWID伪列介绍:
- 二、数据块格式以及ROWID格式
- 1、数据块格式(Data Block Format):
- ① “块开销”部分详解:包含公共与可变头部(Common and Variable Header)、表目录(Table Directory)、行目录(Row Directory)三个部分
- ② 行数据(Row Data)部分:
- ③ 空闲空间(Free Space)部分:
- 2、ROWID格式(Rowid Format):
- 案例一:通过ROWID对表数据进行查询、更新、删除
- 一、通过ROWID对表数据进行查询
- 二、通过ROWID对表数据进行更新
- 三、通过ROWID对表数据进行删除,或对完全重复的行数据进行删除
- 案例二:观察数据存储,分析表性能
一、ROWID伪列介绍:
对于数据库中的每一行数据都会存在ROWID这个伪列字段,ROWID 伪列会返回该行的
物理地址
。Oracle 数据库的 rowid 值包含定位行所需的信息:
- 行数据的对象编号
- 行数据所在的数据文件号(首文件为 1)。文件编号相对于表空间。
- 行数据所在的块号(首行为 0)
- 行号(块中的第几行数据,从0开始计算)
通常,rowid是一行数据的唯一标识。但存储在相同簇中的不同表中的行可能具有相同的 rowid。
ROWID 的数据类型可以为 ROWID 或 UROWID。在下面会详细对比ROWID 与 UROWID 的区别。
ROWID 绝不能用作表的主键,因为行的 rowid 在其生命周期内可能会因数据移动、导出导入等操作而改变。例如,若使用expdp(exp)导出impdp(imp)导入工具删除并重新插入行,其 rowid 可能会发生变化。删除某行后,Oracle 可能将其 rowid 重新分配给后续新插入的行。
虽然可以将rowid作为where条件进行查询、删除、更新对应的数据,但这些伪列值并非实际存储在数据库中,因此无法对ROWID这个值进行插入、更新或删除,因为ROWID是伪列,它不由用户控制,而是由 Oracle 数据库根据数据将要存放的物理位置自动分配和管理的。
ROWID伪列的用途:
- 是访问单行的最快途径。
- 可显示表中行的存储方式
- 是表中行数据的唯一标识符。即使多条一模一样的数据,rowid也是不同
- 可以将rowid作为where条件进行查询、删除、更新对应的数据
ROWID 与 UROWID 的区别对比:
特性维度 ROWID (物理行地址) UROWID (通用行地址) 标识对象 堆组织表 中的行。这是最常见的普通表。 索引组织表 和 外部表(如通过网关连接的其它数据库表)中的行。 地址本质 物理地址:直接指向行在磁盘上的 精确物理位置。 逻辑/外部地址:
- 索引组织表: 基于主键的 逻辑地址。
- 外部表: 由外部系统定义的 外部地址。生成方式 由 Oracle 数据库 根据行的物理存储信息(文件号、块号、行号等)自动生成。 索引组织表 的逻辑 UROWID 由 Oracle 根据主键 生成。
外部表 的 UROWID 由 外部数据源 提供。稳定性 不稳定。只要行的物理位置发生变化,ROWID 就会改变。例如:
- 行被更新并移动到新的块中。
- 使用 ALTER TABLE ... MOVE 移动表。
- 使用导入/导出工具。索引组织表:相对稳定。只要 主键值不变 ,其逻辑 UROWID 就 不变,即使索引叶节点发生了移动。
外部表:取决于外部系统。内容组成 包含 物理位置信息:
1.行数据的对象编号
2.行数据所在的数据文件号(首文件为 1)。文件编号相对于表空间。
3.行数据所在的块号(首行为 0)
4.行号(块中的第几行数据,从0开始计算)索引组织表 (逻辑UROWID): 包含 主键的逻辑标识 ,以及一些“猜”的物理位置信息(用于提高性能,但可能失效)。
外部表 (外部UROWID): 包含外部系统定义的地址信息。编码显示 采用 Base64 编码(A-Z, a-z, 0-9, +, /),如 AAAPecAAFAAAABSAAA。 格式取决于其类型,逻辑 UROWID 也通常显示为 Base64 编码字符串。 如何查看 查询伪列 SELECT ROWID, ... FROM heap_table; 查询伪列 SELECT ROWID, ... FROM iot_table_or_foreign_table; (伪列名称依然是ROWID,但其数据类型是 UROWID) 存储与使用 数据类型为 ROWID 。可以创建此类型的列,但不建议存储,因其会失效。 数据类型为 UROWID 。如果需要存储索引组织表的行地址,应定义 UROWID 类型的列来存放。 解析工具 ROWID是行的物理地址,如果想看行数据的对象编号、行数据所在的数据文件号等信息,就需要使用 DBMS_ROWID 来解释行ID内容。该包函数提取并提供上述四个行ID元素的信息。 逻辑 UROWID 也可以用 DBMS_ROWID 包进行一定程度的解析。 本质 ROWID 和 UROWID 都是 Oracle 用于唯一标识数据库中某一行记录的 地址 或 指针 。它们本身并不作为一个数据值存储在数据块的任何部分。 它不是一个“存储”出来的值,而是一个“计算”出来的结果。 如何选择与记忆 绝大多数情况遇到的是 ROWID:当你使用 SELECT ROWID ... 查询一个标准创建的普通表时,你看到的就是 物理 ROWID 。记住它 会变 ,不要依赖它作为永久标识。
需要处理索引组织表时才需显式关注 UROWID:如果你在设计中使用索引组织表,并且需要存储行地址,那么你应该使用 UROWID 数据类型的列来存储 ROWID 伪列的值,因为它是相对稳定的逻辑地址。
简单类比:
·物理 ROWID 像是一个 邮政编码+街道地址+门牌号 ,直接指向一个物理位置。如果房子被拆了或搬了,这个地址就失效了。
·逻辑 UROWID 像是根据一个唯一身份ID(如主键) 生成的地址。只要这个人的ID不变,你总能通过系统找到他,即使他换了房子住。
那么知道了Oracle中的每行数据都有ROWID这个伪列,行数据呢又是存储在数据块中的,总所周知,Oracle的逻辑存储结构从大到小为表空间—>段—>区—>块。那么块就是Oracle管理数据文件中存储空间的单位,为数据库使用的 I/O 的最小单位。那么下面就从数据块的结构开始介绍,再到了解ROWID的格式。
二、数据块格式以及ROWID格式
1、数据块格式(Data Block Format):
每个数据块都具有特定的格式或内部结构,使数据库能够跟踪块中的数据与空闲空间。无论数据块包含表、索引还是表簇数据,其格式都基本相似。
下图是博主手绘了一个未压缩数据块的格式。
上图可以看到一个数据块由5个部分组成:
公共与可变头部(Common and Variable Header)、表目录(Table Directory)、行目录(Row Directory)三个部分
用来记录数据块的其他信息,这三个部分统称为“块开销”,“块开销”这部分不存储真实的数据。行数据(Row Data)部分
是已经用掉的部分,真实存储表中的行数据。空闲空间(Free Space)部分
是还没有使用的部分,等待行数据的写入。
① “块开销”部分详解:包含公共与可变头部(Common and Variable Header)、表目录(Table Directory)、行目录(Row Directory)三个部分
公共与可变头部(Common and Variable Header): 包含块的通用信息,如
磁盘地址
(属于哪个数据文件、哪个块)、段类型
(是表段、索引段还是其他)、事务管理信息
(对于支持事务的段,包含活跃和历史事务信息)。
事务槽(Transaction Slots/Entries)这是头部的重要组成部分。每个更新此块的事务都需要一个事务槽。Oracle最初在头部预留空间给事务槽,如果不够,会使用空闲空间。每个事务槽通常占用约 23字节(操作系统依赖)。表目录(Table Directory): 对于
堆表
,此目录包含在该块中存储行数据的表的元数据。对于表簇
(Cluster),多个表的数据可以存储在同一个块中,表目录就记录了这些表的信息。行目录(Row Directory) : 对于堆表,行目录描述了行在块数据部分中的位置。数据库可以将行放置在块底部的任意位置。行地址会被记录在行目录向量的某个槽位中。它可以看作是一个“指针数组”或“索引”。每个槽位(Slot)指向行数据部分中一条行片段(Row Piece) 的实际起始位置。
关键特性:
- 即使删除了块中的所有行,为行目录分配的空间(例如,曾最多有50行,则可能预留100字节)也不会被回收,只会在后续插入新行时被重用。这就是为什么一个空块可能仍占用空间的原因之一。
- 对于行地址(rowid)而言,rowid指向特定的文件、块和行号。例如rowid值为AAAPecAAFAAAABSAAA中,ROWID 中的最后一部分(如 AAA)其实就是行目录的槽位索引,该条目包含指向数据块中行位置的指针。如果数据库在块内移动行,会更新行目录条目以修改指针,但rowid保持不变。
总结一下,块开销的某些部分是固定大小的,但总大小是可变的。平均而言,块开销总计在84至107字节之间。
② 行数据(Row Data)部分:
数据块中的行数据部分包含实际数据,如表行或索引键条目。正如每个数据块都有内部格式,每一行也具有特定的行格式,使数据库能够跟踪行中的数据。
Oracle数据库将行存储为可变长度记录。一行包含一个或多个段,每个段称为行片段。每个行片段包含行头部和列数据。
下图手绘了行的格式。
上图可以看到一行数据由2个部分组成:
行数据头部(Row Header)部分
Oracle数据库使用行头部来管理存储在数据块中的行片段。列数据(Column Data)部分
在行头部之后,列数据部分存储行中的实际数据。
行数据头部(Row Header):
Oracle数据库使用行头部来管理存储在数据块中的行片段。行头部包含以下信息:
- 该行片段中的列信息
- 位于其他数据块中的行片段: 如果整行可插入单个数据块,则Oracle数据库将行存储为一个行片段。但当行数据无法全部插入单个块,或更新操作导致现有行超出原块容量时,数据库会将行存储为多个行片段。通常,一个数据块中每行仅包含一个行片段。
对于表簇:
- 簇键信息: 完全存储于单个块中的行至少包含3字节的行头部。
列数据(Column Data):
在行头部之后,列数据部分存储行中的实际数据。行片段通常按照CREATE TABLE语句中列出的顺序存储列,但该顺序并不绝对保证。例如,LONG类型的列总是最后创建。
如"行格式"中的图所示,对于行片段中的每个列,Oracle数据库分别存储列长度和数据,所需空间取决于数据类型。若列的数据类型为可变长度,则存储值所需空间可随数据更新而扩展或收缩。
每行在数据块头部的行目录中都有一个槽位。该槽位指向行的起始位置。
③ 空闲空间(Free Space)部分:
这是块中尚未使用的空间,用于后续的 插入新行 和 现有行的更新 (更新可能导致行增长)。当公共与可变头部(Common and Variable Header)部分的 事务槽 空间用尽时,空闲空间也会被用来分配新的事务槽。
那么关于数据块的介绍到这里就算完成了,那么通过上面的内容, 了解到ROWID本身并不作为一个数据值存储在数据块的任何部分 。它不是一个“存储”出来的值,而是一个“计算”出来的结果。 构成ROWID的所有原始信息(元数据)都明确地存储在数据块的【块开销(Block Overhead)】里,更准确地说,是【行目录(Row Directory)】中的槽位索引号与【公共和可变头部】中的磁盘地址和数据对象号等信息共同组合计算后的一种表现形式,其本身并不独立存储。 那么下面开始介绍ROWID的格式。
2、ROWID格式(Rowid Format):
堆组织的表中的每一行都有一个rowid,这个rowid对于这个表是唯一的,它对应于一个行段的物理地址。rowid是一行的10字节物理地址。
rowid指向特定的文件、块和行号。例如,rowid为AAAPecAAFAAAABSAAA,最后一个AAA表示行号。行号是行目录条目的索引。行目录条目包含指向块中行位置的指针。博主手绘了一张rowid的逻辑解析图。
rowid以四段格式显示,OOOOOOFFFBBBBBBRRR,该格式分为以下几个部分:
- OOOOOO: 数据对象编号标识该段(在示例中为AAAPec)。数据对象编号被分配给每个数据库段。同一段中的模式对象(如表簇)具有相同的数据对象编号。
- FFF: 与表空间相关的数据文件号标识包含该行的数据文件(在示例中为AAF)。
- BBBBBB: 数据块编号标识包含该行的块(在示例中为AAAABS)。块号与它们的数据文件相关,而不是与它们的表空间相关。因此,具有相同块号的两行可能位于同一表空间的不同数据文件中。
- RRR: 行号标识块中的行(在示例中为AAA)。
ROWID 的组成部分 实际存储在数据块的哪个部分 说明 数据对象号 (Data Object Number) 公共和可变头部 (Common and Variable Header) 标识这个数据块属于哪个表(段)。这是段的元数据信息。 数据文件号 (Data File Number) 公共和可变头部 (Common and Variable Header) 标识块所在的数据文件。这是块的磁盘地址信息的一部分。 数据块号 (Data Block Number) 公共和可变头部 (Common and Variable Header) 标识块在文件中的具体位置。这是块的磁盘地址信息的一部分。 行号 (Row Number) 行目录 (Row Directory) 这是最关键的一环。“行号”实际上是行目录中一个槽位(Slot)的索引号(例如,第0个槽位、第1个槽位)。每个槽位里存储的是一个指针,指向行数据部分中对应行的实际起始位置。ROWID中的行号(如AAA)就是这个索引号。 在某些特殊情况下,rowid 可能会发生变化,尤其是在启用了行移动功能时。例如,rowid 可能会因为以下原因而改变:分区键更新、闪回表操作或收缩表操作等。如果禁用了行移动功能,那么在使用 Oracle 数据库的导出和导入工具时,rowid 也会发生变化。
Oracle数据库在内部使用rowids来构建索引
。例如,B树索引中的每个键都与指向相关行地址的rowid相关联。物理rowids提供了对表行最快的访问,使数据库只需一次I/O就可以检索一行
。那么直接使用rowid进行查询,比查询索引列检索I/O的次数还要少,例如last_name有索引,也需要在索引块和数据块之间进行两次I/O操作:先在索引中查找last_name = ‘King’,得到ROWID,再根据ROWID去表中取数据。
索引块I/O (第一次I/O):
- 数据库首先在内存中查找是否缓存了索引的根块、分支块。如果没有,就必须
从磁盘物理读取
所需的索引叶子块(Index Leaf Block)到内存中。- 数据库遍历索引树,在索引叶子块中找到键值为
'King'
的条目。这个条目存储的就是对应行的 ROWID。
表数据块I/O (第二次I/O):
- 数据库拿到第一步中得到的
ROWID
。这个ROWID包含了文件号、块号。- 数据库检查这个目标数据块是否已经在内存(Buffer Cache)中。如果不在,就必须
再次从磁盘物理读取
这个指定的数据块到内存中。- 读取到数据块后,数据库利用ROWID中的行号(即行目录索引)在块内精确定位到该行,并返回数据。
案例一:通过ROWID对表数据进行查询、更新、删除
小提示:如下的更新、删除案例是基于ROWID作为where条件对真实的数据进行操作,而不是直接对ROWID值进行更新、删除。并且ROWID这个值是不能进行手动插入的,因为ROWID是伪列,它不由用户控制,而是由 Oracle 数据库根据数据将要存放的物理位置自动分配和管理的。
操作 是否可用 说明 SELECT ✅ 强烈推荐 最快的单行查询方式。 UPDATE ✅ 强烈推荐 最快的单行更新方式,尤其在循环批处理中。 DELETE ✅ 强烈推荐 最快的单行删除方式,也是去重操作的关键技术。 INSERT ❌ 禁止 ROWID?是系统管理的伪列,插入时不能手动指定。 创建测试表和插入测试数据
-- 创建表 CREATE TABLE employees_demo (employee_id NUMBER(6),first_name VARCHAR2(20),last_name VARCHAR2(25),email VARCHAR2(25),hire_date DATE DEFAULT SYSDATE,salary NUMBER(8,2),department_id NUMBER(4) );-- 为last_name和department_id创建索引以提高初始查询速度 CREATE INDEX emp_demo_name_idx ON employees_demo(last_name); CREATE INDEX emp_demo_dept_idx ON employees_demo(department_id);-- 插入测试数据 INSERT INTO employees_demo (employee_id, first_name, last_name, email, salary, department_id) VALUES (100, 'Steven', 'King', 'SKING', 24000, 90);INSERT INTO employees_demo (employee_id, first_name, last_name, email, salary, department_id) VALUES (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000, 90);INSERT INTO employees_demo (employee_id, first_name, last_name, email, salary, department_id) VALUES (102, 'Lex', 'De Haan', 'LDEHAAN', 17000, 90);INSERT INTO employees_demo (employee_id, first_name, last_name, email, salary, department_id) VALUES (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000, 60);INSERT INTO employees_demo (employee_id, first_name, last_name, email, salary, department_id) VALUES (104, 'Bruce', 'Ernst', 'BERNST', 6000, 60);-- 插入两行完全重复的测试数据 INSERT INTO employees_demo (employee_id, first_name, last_name, email, salary, department_id) VALUES (105, 'David', 'Austin', 'DAUSTIN', 4800, 60);INSERT INTO employees_demo (employee_id, first_name, last_name, email, salary, department_id) VALUES (105, 'David', 'Austin', 'DAUSTIN', 4800, 60);COMMIT;
一、通过ROWID对表数据进行查询
这是最直接和最常见的用法。通常先通过一个查询获取到目标行的
ROWID
,然后再用它进行后续操作。
传统低效做法:
SQL> SELECT ROWID, employee_id, first_name, last_name FROM employees_demo WHERE last_name = 'King';
###即使last_name有索引,也需要在索引块和数据块之间进行两次I/O操作:先在索引中查找last_name = ‘King’,得到ROWID,再根据ROWID去表中取数据。
高效做法:
-- 使用ROWID进行极速精准查询(后续操作都可用这个ROWID) SQL> SELECT * FROM employees_demo WHERE ROWID = 'AAAXiwAAEAAAFj3AAA';
###使用ROWID访问直接定位到精确的磁盘位置,跳过了索引查找步骤,是Oracle中最快的数据访问方式。这在需要对单行进行高频、极速操作的场景中非常有用。关于两次I/O参考上面的2、ROWID格式(Rowid Format)
二、通过ROWID对表数据进行更新
适用于需要根据当前值进行快速更新的场景,尤其是在循环或高频操作中。
SQL> UPDATE employees_demo SET salary = salary * 1.1 WHERE ROWID = 'AAAXiwAAEAAAFj3AAA'; -- 使用上面获取的ROWID ### 这个更新操作绕过了对 employee_id 或 last_name 列的索引查找,直接定位到数据块进行修改,速度极快。COMMIT;SELECT employee_id, first_name, salary FROM employees_demo WHERE ROWID = 'AAAXiwAAEAAAFj3AAA';
SALARY 字段的数据更新为26400
三、通过ROWID对表数据进行删除,或对完全重复的行数据进行删除
用于快速、精准地删除单条记录。
--查询全表数据,以及行数据对应的rowid值 SQL> SELECT tb1.*,rowid FROM employees_demo tb1;
###可以看到有两行完全一模一样的数据,虽然两行的数据都相同,但行数据的rowid值不同,因为rowid拥有唯一性。我现在只想保留一行,那么就可以通过删除其中一行的rowid实现。
-- 使用ROWID进行精准删除 SQL> DELETE FROM employees_demo WHERE ROWID = 'AAAXiwAAEAAAFj3AAF';COMMIT;SELECT tb1.*,rowid FROM employees_demo tb1;
###可以看到通过rowid删除了另一个完全一模一样的行,对于表中没有主键但有完全重复的行数据时,使用rowid进行精准删除不要太方便
案例二:观察数据存储,分析表性能
如果怀疑一个非常大的表存在 行迁移(Row Migration),即更新操作导致一行数据无法容纳在原始块中,被移动到了另一个块,并在原块留下一个指针。这会导致查询该行需要访问两个数据块,性能下降。
博主先介绍一下表的行迁移与行链接。更详细的介绍参考官方文档:Chained and Migrated Rows
行迁移(row Migration):当前块空间不够,其他块空间充足
数据块中存放着一条一条的数据,每个数据块会预留剩余的空间,便于一条数据的扩展。当剩余的空间不够一条数据的扩展时 (数据不超过一个全块的大小) ,这条数据就需要迁移到可存放这条数据的数据块中,并且需要在原来存放数据的位置放一个指针,用于指向新的数据。迁移数据会导致性能下降。
行链接(row Chaining):当前块空间不够,其他块空间也不够
一条记录的某行内容非常长,任何一个块都放不下 (数据超过了一个全块的大小) 。那么他们会分成不同的部分,每一部分被称为行片(row pieces)。每一个块中都会有指针帮助oracle组装成一条完整的记录。造成row chaining 的根本原因是因为数据块设置的太小了。Row chaining 同样会造成 oracle 的性能下降。如果减少row chaining一种方法是可以甚至增加块的大小,加一个种方法是将表拆分成多个小表。
案例开始 1)查看数据的物理分布:
SQL> SELECT ROWID, dbms_rowid.rowid_block_number(ROWID) AS block_number, EMPLOYEE_ID, FIRST_NAME, EMAIL FROM employees_demo ORDER BY block_number;
###这可以看到哪些行的数据存储在相同的数据块中(block_number相同)。如果数据插入有序,相同范围的数据会集中存储,全表扫描效率会更高。
2)使用DBMS_ROWID包深入分析:
SQL> SELECT ROWID,DBMS_ROWID.ROWID_OBJECT(ROWID) AS data_object_id,DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS file_no,DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS block_no,DBMS_ROWID.ROWID_ROW_NUMBER(ROWID) AS row_no,employee_id,last_name FROM employees_demo ORDER BY block_no, row_no; -- 按物理存储顺序排序
从这个结果可以看到,这些行都存储在同一个数据块(BLOCK_NO: 27535)中,说明初始插入的数据在物理上是紧密相邻的。
以其中的一个rowid为例: AAAXi0AAEAAAGuPAAA | 96436 | 4 | 27535 | 0
data_object_id: 96436: 这是EMPLOYEES段的唯一ID。
file_no: 4: 数据存储在相对文件号4中。
block_no: 27535: 数据存储在文件的第27535个块中。
row_no: 0: 这是该块中的第0行(第一行)。
完结,撒花。那么总结一下🎯,地址即访问,ROWID 以其最直接的方式,揭示了数据库高效操作的终极奥义:减少寻址开销,直达数据本身 🎯。