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% 时表示这个块又可以继续插入新数据

相关推荐
闲云野鹤在人间2 小时前
MySQL|从理论、安装、备份到主从复制、MHA高可用详解
linux·运维·数据库·mysql·云计算
胖头鱼的鱼缸(尹海文)2 小时前
胖头鱼的技术专栏-474 从能跑到能上线:PG SplitJSON 0.2.0 和 0.1.0 差在哪(20261008)
postgresql·json
要开心吖ZSH3 小时前
MySQL 慢 SQL 排查操作手册-个人笔记版
java·笔记·sql·mysql·慢查询
Gl�ria5 小时前
MySQL 单机版 vs 高可用版:宕机排查 + 故障处理
mysql·adb·架构
Titan20246 小时前
MySQL索引学习笔记
笔记·学习·mysql
云贝贝贝6 小时前
PostgreSQL 分区表:从设计到运维,大表不再卡
运维·数据库·postgresql
oradh6 小时前
Oracle固定执行计划的方法---SQL Plan Baseline
数据库·sql·oracle
ClouGence6 小时前
数据库迁移工具 CloudCanal v6.5.0.0 发布:新增 TDSQL PostgreSQL 多条链路,支持 MongoDB 双向同步
数据库·mysql·mongodb
这个DBA有点耶8 小时前
Change Buffer深入:二级索引写入的隐形加速器与它的代价
数据库·mysql·代码规范
for_ever_love__8 小时前
MySQL 事务隔离级别讲透:MVCC、幻读与四个级别怎么选
java·数据库·mysql·事务·mvcc·不可重复读·幻读