Oracle索引组织表,SQLServer聚集索引,Mysql Innodb表的数据行不是在磁盘上像排队一样严格连续地存放

MySQL InnoDB表 ,Oracle 的索引组织表 (IOT),SQL Server 的聚集索引,本质上是同一个东西,就是按照主键索引的 B-Tree 结构来组织的,索引非叶子节点只存索引键值 + 指向子节点的指针,索引叶子节点保存了完整的行记录。表中的数据行是按主键排序顺序存储的,并不是指数据在磁盘上像排队一样严格连续地存放,而是指数据在逻辑上按主键顺序存储。

InnoDB表Table以ID列为主键,ID有100到800的8行数据,400是索引非叶子节点只存索引键值100到400的这4行,800是索引非叶子节点只存索引键值500到800的这4行,100是行1的列,200是行2的列,行1和行2在逻辑上是顺序存储的(就算随机插入时遇到块的空间不够,也有Mysql InnoDB页分裂机制,Oracle行迁移机制这样的类似机制保证这个顺序),但是行1的物理位置和行2的物理位置可能相邻(顺序插入的情况下,2行数据在同一个数据块或相邻数据块),也可能相差十万八千里(随机插入的情况下,2行数据分散到了相隔很远的数据块)

400----------800(非叶子节点)

100 200 300 400 500 600 700 800(叶子节点)

行1 行2 行3 行4 行5 行6 行7 行8

SQL Server官方文档https://learn.microsoft.com/zh-cn/sql/relational-databases/indexes/clustered-and-nonclustered-indexes-described?view=sql-server-ver17

聚集索引根据数据行的键值在表或视图中排序和存储这些数据行。

只有当表包含聚集索引时,表中的数据行才按排序顺序存储。 如果表没有聚集索引,则其数据行存储在一个称为堆的无序结构中。

Mysql官方文档https://dev.mysql.com/doc/refman/9.6/en/innodb-physical-structure.html

当将新记录插入 InnoDB 聚簇索引 时,InnoDB 尝试在页中保留 1/16 的空间以供将来插入和更新索引记录。如果索引记录按顺序插入(升序或降序),则生成的索引页大约是 15/16 满的。如果记录是随机插入的,则页面是 1/2 到 15/16 满的。

Oracle ROWID

https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/ROWID-Pseudocolumn.html

For each row in the database, the ROWID pseudocolumn returns the address of the row

ROWID 主要记录以下四部分信息:数据对象编号、数据文件编号、数据块编号、行编号。数据对象编号、数据文件编号、数据块编号、行编号都是数据库内部的逻辑单元,而不是磁盘物理地址(柱面/磁头/扇区),但是我们可以通过ROWID这个值快速定位到它在磁盘上的物理地址。

官方文档从来没说这个ROWID是行在磁盘上的物理地址比如柱面/磁头/扇区,当MySQL InnoDB表 ,Oracle 的索引组织表 (IOT),SQL Server 的聚集索引没有主键而是使用ROWID来当主键排序存储时,这个ROWID值本身是顺序的(就算随机插入这个时候也有Mysql InnoDB页分裂机制,Oracle行迁移机制这样的类似机制来保证顺序ROWID值本身顺序),ROWID并不代表行在磁盘上像排队一样严格连续地存放,只不过可以通过这个ROWID可以快速定位到哪个文件哪个块,Oracle 知道需要读取哪个文件的哪个块后,会通过系统调用请求操作系统读取该数据块。操作系统则负责将文件中的逻辑偏移量(块编号 × 块大小)转换为磁盘上的物理扇区地址,最终完成读取。

Postgresql ctid

https://www.postgresql.org/docs/current/ddl-system-columns.html

The physical location of the row version within its table. Note that although the ctid can be used to locate the row version very quickly, a row's ctid will change if it is updated or moved by VACUUM FULL. Therefore ctid should not be used as a row identifier. A primary key should be used to identify logical rows.

行版本在其表中的物理位置(不代表磁盘上的物理地址比如柱面/磁头/扇区)。注意,尽管 ctid 可用于极快地定位行版本,但如果行被更新或被 VACUUM FULL 移动,行的 ctid 会发生变化。因此,ctid 不应作为行标识符使用。应使用主键来标识逻辑行。

那么一张表有主键ID有数据1亿零1条记录,但是数据是从1,3,4开始,没有2,如果这个时候插入2,因为2得排在3,4之前,是不是3,4后面的1亿条数据都得重新挪位置?如果是这样的话,插入2就会引发2 后面的所有数据(约1亿条)都往后挪一位,那这样的操作在任何生产系统上都是无法接受的。

InnoDB 引擎当然也想到了这一点,它并没有采用这种低效的"物理重排"方式。实际上,InnoDB 通过页(Page) 结构和页分裂(Page Split) 机制,以一种相对轻量级的方式解决了这个问题。InnoDB 通过页(Page) 结构中有提到如果索引记录按顺序插入(升序或降序),则生成的索引页大约是 15/16 满的。

Oracle

行链接:INSERT一行时,这行太长,导致数据块没有可用空间装下这一行,这时会将一行数据拆分成多片,分散存储在多个数据块中。读取一行数据,本来读一个数据块就够了,现在要读 2 个甚至更多块,I/O 次数成倍增加。

行迁移:UPDATE一行时,行原本能装下,但更新后变长了,原块没有足够剩余空间,将整行数据整体搬家到一个新块,原块只留下一个指针指向新块。读取一行数据,本来读一次 I/O,现在变成:先读老块(找到指针) + 再读新块(找到数据) = 至少 2 次 I/O。对于频繁通过索引访问的表,性能下降会非常明显。

Oracle 数据块存储参数 PCTFREE默认值 10%:当这个块的剩余可用空间只有10%时,这个块不能再插入新数据,只能用于已有数据的更新操作

Oracle 数据块存储参数 PCTUSED默认值 40%:当这个块的使用率降到 40% 时表示这个块又可以继续插入新数据

相关推荐
AI开源软件12 小时前
SpringBoot信创项目怎么验收?从数据库迁移到交付证据
postgresql·springboot·达梦·opengauss·信创·企业管理系统
1024奇点13 小时前
【仓颉语言入门 · 第二篇 第 12 课】
开发语言·数据库·ide·mysql·开源
冰暮流星13 小时前
mysql之insert优化
数据库·mysql
北方的银狐-Zero13 小时前
OntoL 7.0|面向工业多智能体的语义本体套件,让异构系统真正 “看懂彼此”——OntoL的本体产品矩阵
人工智能·mysql·sqlite·1024程序员节·本体论·本体产品
海鸥-w13 小时前
数据库冗余字段设计笔记
java·数据库·笔记·oracle
瑞克-桑切斯14 小时前
4、MySQL 索引与事务
数据库·mysql
二宝哥14 小时前
【问题】本地启动springboot项目启动flowable引擎会提示数据库脚本错误,同事本地启动没有报错(MySQL表明大小写敏感参数)
数据库·mysql
l1t15 小时前
DeepSeek总结的ALTER FUNCTION SET work_mem:无需全局更改即可修复 PostgreSQL 磁盘溢出
sql·缓存·postgresql
瑞克-桑切斯15 小时前
2、 MySQL 主从复制与读写分离
数据库·mysql