暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

人大金仓 金仓数据库KingbaseES ROWID

数据猿 2023-08-11
1227



关键字:

KingbaseES、ROWID


1.什么是ROWID?


ROWID是数据库的一个伪列,建立表的时候数据库会自动为每个表建立ROWID列,是数据库中每一条记录的唯一标识,存储每条记录的时间物理地址,对记录的访问是基于ROWID。但它实际上并不存储在表中,可以从表中查询,但不支持插入、更新、删除他们的值。


2.ROWID分类


  • 物理ROWID-普通的堆表中的ROWID是物理ROWID。


AAAX0sAAMAAAGt1AAA 1 1234567890 1

  • 逻辑ROWID-索引组织表IOT的ROWID是逻辑ROWID。


*BAMAa3sCwQL+ 1 abc
*BAMAa3sCwQP+ 2 abc

  • 外部ROWID-保存外部表的标识。


3.ROWID用途


  1. ROWID可以作为特殊行的快速访问
  2. ROWID可以判断表的组织形式
  3. 可以作为一个表的行唯一标识

4.ROWID伪列和ROWID格式


ROWID 伪列为表中非实际存储的列,只能用于查询并不能insert,update,delete。ROWID 返回一个ROWID 类型的字符串,代表每一行的实际地址,ROWID 字符串由64位编码组成。编码字符包括A-Z,a-z,0-9,+和/, 参考下图为Oracle ROWID格式。




包含4个部分:OOOOOOFFFBBBBBBRRR

1. OOOOOO, 总计6个字符,表示 data object number identifies the segment. 实际上就是这个对象对应的段id。

2. FFF 表示表空间的相对文件号,根据相对文件号可以得到绝对文件号,从而确定datafile


3. BBBBBB 表示data block number 相对于本datafile 的编号,而不是tablespace 的编号。

4. RRR 表示row number 即此行在data block 内的编号。

根据oracle 《concept》 文档中说明,row number 本身并不占用数据库的实际存储,是通过file 和 block 实际存储的位置推断出来的值。

5.KES ROWID能力


基于现有框架,扩充并完善了ROWID功能,包括以下几个方面:

  1. 新增一个参数,开启后为每个表增加一个隐含列“ROWID”, ROWID 存储一个唯一确定改行的ID。
  2. 该参数开启之后,原default_with_oids 的参数就会失效。
  3. 新参数开启后,新增的ROWID 隐含列,位于表的首列,是一个单调递增的序列。且为其建有索引,索引类型为唯一btree 索引,以提升查询性能。
  4. 为了防止开启参数后大量表都会增加rowid 隐含列,在少数需要rowid 的情况下,提供相关的DDL 语法。

6.KES和Oracle直接ROWID的本质区别


KES 的 ROWID 或者说是类比于ROWID 的相关功能实际上都实际存储在数据库中,而Oracle 的ROWID 实际上是伪列,没有实际的存储,它的值是通过行在整个数据库系统中的偏移推断出来的。然而,KES 如果也通过存储推断,由于update非本地更新,进而导致存储位置会发生变化。KES 的default with oids 等功能,通过表自建的sequence和隐含列对sequence的直接存储保证了表中的内建列里面,因此需要占用存储空间。但这也是无奈之举,虽然我们在遍历表上面的元组的时候也可以推测出元组在数据库中的实际存储位置,但这个位置会伴随着update 和vacuum 发生变化,不稳定因此不能用于where 条件进行定位。



7.ROWID的选取和表征


KES 目前系统的存储框架不能保证行在数据库中地址的唯一,但其插入的地址是唯一的,当该元组被insert 后我们可以将其插入的位置用某种唯一确定的编码或id 进行存储,在后面的update、vacuum 过程中此值不会发生变化。这个id 的选择可以从以下几个方面入手:

自建sequence,参考default_with_oids 的方法,为每个表建有seq,此方法的优势在于可以保证rowid 大致连续,可读。但有个致命的确定,并发insert 会争抢seq 的资源导致并发insert 变成串行化,且引入了额外的日志和IO, 性能不好。


记录INSERT record的LSN 号, insert 的LSN 是该元组在日志中的唯一标记,唯一地址,因此可以很好的表征该元组在数据库中(实际上是wal日志)存在的位置。但对于MULTI INSERT 而言存在多条insert record 公用一个LSN 的情况,实际操作的时候也是先写数据,再申请LSN 号,因此如果再拿到LSN 后反更新表中数据也存在很大的风险。除此之外LSN 号不具有可读性,并不能表征元组在数据库中的存储位置。并且对于临时表、unlogged 表都没有办法用LSN 号表征。


通过事务信息和CTID 的组合表征元组ID, 我们知道CTID 之所以不能写入rowid 是因为vacuum之后,被删除的槽位会被分配处理,结果会导致该槽位被用于新元组,因而导致了rowid 出现重复。倘若我们在将额外的事务信息也记录在rowid 列上,就可以避免rowid 的重复了,这种方法即不会重复,也具有可读性。同时为了避免事务的回卷,还需要额外的存储回卷次数。综上我们选取方案3)存储rowid 列作为行在数据库中的唯一标记。

针对回卷次数,我们考察了系统记录在controldata 中的 FulltransactionId, 当txid 超过了42亿后,其NextXid 高32位会+1,假如我们取其低8位作为回卷次数。假设我们数据库的TPMC在100万,那么一个小时会消耗6000万个xid,42亿xid 消耗完需要7天,那么256次回卷大概需要5年左右。若取16位作为回卷次数,4096次回卷用尽则需要78年,但如果选取的长度越大所需的存储空间也就越大。





问题分析,考虑对于分区表这样的特殊结构,上述的表征方法,对于同一个事务插入不同分区会导致不同分区表中拥有重复的rowid,因此不能保证rowid 唯一,所以变更一下rowid的组成。

由原来的回卷次数+txnid + blocknum + offset 四部分,变为

回卷次数 + txnid + 本次事务内已插入的元组数

前两个用于区分不同事务间的元组写入,后一部分用于区分事务内不同元组的写入。

8.ROWID内置类型


8.1 ROWID内置类型定义


ROWID 为KES 内置类型,用户可以通过其创建属于ROWID 类型的列。 KES 的ROWS 在数据库中存在逻辑上的唯一标识(并非物理地址)通过ROWID 类型的数据进行表示,ROWID 一般的表示方法通过64进制的字符进行表示, 总长度88位。

包含以下几部分信息:


事务回卷次数

元组插入时的xid

事务内已插入元组的个数

32位

32位

32位

ROWID type 类型的数据在数据库存储是通过内部的复合结构进行存储,也通过不同的成员标识不同信息。

ROWID type 的输入、输出都以64位编码格式,包含A-Z,a-z,0-9,+以及/符号。

ROWID 的范围如下:

 

事务回卷次数

元组插入时的xid

事务内已插入元组的个数

 

32位

32位

32位

Min

AAAAAA

AAAAAB

AAAAAAAAAAA

Max

D//////

D//////

P//////////

超出以上范围的ROWID 都属于非法格式的ROWID。

相比于16进制输出而言,64位编码输出格式上可以省去5个字符。

注,每次开启一个新的事务(包含子事务),事务内已插入元组的个数都重新计算。


create table rowed_t1(col1 rowid,col2 "rowid");-- 创建列
insert into rowed_t1 values('AAAAAAAAAAABAAAAAAAAAAA','D/D/P//');-- 通过符合ROWID格式的字符串输入可以插入ROWID类型中
select * from rowed_t1 where col1 = 'AAAAAAAAAAABAAAAAAAAAAA';-- 可以通过ROWID 进行表达式比较




8.2 ROWID内置类型的操作符


ROWID 内置类型的操作符用于表征该类型能够支持的数据的操作,数据转换,索引操作。

包括以下几个方面:

1. 支持数据的in、out操作,一般而言数据输入通过符合ROWID 格式的字符串进行转换。输出也需要通过将内部存储的数据转换为64位编码的可读性质的字符串。

导入:

通过insert将符合ROWID格式的字符串数据插入到数据库中

INTO T1 VALUES(‘AFAAFFFXAXXAFFAAx’, 1);

或 enable_ci(不支持), 隐式转换不支持

COPY TO T1; 解析文件格式的字符串符合ROWID 格式。

导出:


通过查询、copy、dump 将ROWID 类型的列导出到目标文件或者前端。

2. 支持数据比较操作符,仅支持 =,>, >=, <, <=, != 以及同类型的比较操作符。
可通过where 条件,表达式比较操作符可以指定rowid 类型, 并且具有实际的逻辑意义。

=:rowid 上各个位置信息严格相等。

!=: rowid 上各个列位置信息严格不相等。

>=(类): 先比较回卷位,在通过事务xid 进行过去、未来判定,最后比较ctid 位。

原则上,后插入的数据的rowid 要大于先插入的rowid,即rowid 属于单调递增的数据。

其他数据运算符报不支持ROWID 类型的错误。
其他数据类型操作符进行rowid操作予以报错处理。

3. 支持 ROWID 对于BTREE 索引的操作符,使其能够创建btree 索引,其他类型索引不支持。
CREATE TABLE rowid_t2(col1 rowid);

\echo `echo 'AAAAAAAAAAABAAAAAAAAAAA' > /tmp/rowid_t2.txt`

COPY rowid_t2 FROM '/tmp/rowid_t2.txt';

select * from rowid_t2 ;

copy rowid_t2 to '/tmp/rowid_t2_to.txt';

\echo `more /tmp/rowid_t2_to.txt`
insert into rowid_t2 values('AAAAAAAAAAABAAAAAAAAAAY');

select * from rowid_t2 where col1 < 'AAAAAAAAAAABAAAAAAAAAAY';

select col1 * 2 from rowid_t2;-- error





9.为表增加ROWID隐含列功能


9.1 ROWID 列的定义和约束


由于KES 的ROWID需要实际存在表中,并且需要构造唯一btree 索引,因此默认情况下是不加ROWID 列的。当需要为一个表增加ROWID,可通过为用户增加的GUC变量或者DDL 语句增加ROWID, 本节主要描述ROWID 列的定义类型和相关的约束。

ROWID伪列有以下约束条件

ROWID 列增加后始终位于表的第一列(alter 除外),且为ROWID 类型,是隐含列,但不可修改为非隐含列。
列定义后会为该列自动创建一个btree 唯一索引,用于where 条件的快速查询。
该列不允许用户insert、update 操作,仅能通过内部接口进行更改。
该列的查询特性必须手动指定rowid 才可以访问。
Rowid 列的写入,在数据第一次入库的时候,通过当前xid,wrap id和ctid 进行设置,存储到rowid 列之中。
逻辑同步不应该同步rowid,应该在备库中新写入元组的时候自动产生rowid。

set default_with_rowid = on;

create table rowid_t3(col1 "rowid");

alter table rowid_t3 alter column "rowid" visible;-- error





select * from sys_indexes where tablename = 'rowid_t3';





insert into rowid_t3("rowid") values('AAAAAAAAAAABAAAAAAAAAAA');-- error

insert into rowid_t3 values('AAAAAAAAAAABAAAAAAAAAAA');

select "rowid" as rowid_data from rowid_t3 \gset

update rowid_t3 set "rowid" = 'AAAAAAAAAAABAAAAAAAAAAAX' where "rowid" = :'rowid_data';-- error







9.2 GUC 参数控制批量增加ROWID列


为了批量的为KES 数据库中个各个表增加ROWID 隐含列,增加一个session GUC参数default_with_rowid, 该参数开启以后,再通过系统数据库创建的表会带有一个ROWID的隐含列。

GUC 参数名称: default_with_rowid。
参数类型: bool类型
参数默认值: false,不创建rowid 隐含列。
参数设置范围:session、ctrl 文件、alter system。
参数作用,开启后为系统上所有再创建的表增加一个rowid 名,类型为rowid type的隐含列。且所有插入的数据,都会将当前行的rowid 写入到该列之中。
限制: 该参数和default_with_oids的关系,两者面向不同的应用,同时当一方设置为true 时,通过DDL 非批量创建表增加rowid或oid 隐含列也会失败。(rowid 的参数优先级高于oids)。

alter system set default_with_rowid = true;

call sys_reload_conf();

show default_with_rowid;

create table rowed_t4(col1 int,col2 varchar(20));

insert into rowed_t4 values(1, 'A');

select rowid,* from rowed_t4;





create table rowid_t5(col1 int);

insert into rowid_t5 values(1);

select rowid,oid from rowid_t5;-- error


9.3 DDL 语句为单表增加ROWID列


当不期望通过批量建表创建rowid 隐含列的时候,可以通过在DDL(create、alter)两类语句,进行表级ROWID列的创建或添加。

在default_with_oids 开启作用下会报错,会提示用户不能创建带有rowid 隐含列的表。

create table rowid_t6(col1 int) with rowid;

insert into rowid_t6 values(1);

select rowid,* from rowid_t6;





create table rowid_t7(col1 int);

alter table rowid_t7 set with rowid;

insert into rowid_t7 values(1);

select rowid,* from rowid_t7;





set default_with_oids = true;

create table rowid_t8(col1 int) with rowid;-- error







「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论