RowID
1. 目的
IvorySQL提供了兼容Oracle RowID的功能。RowID是一种伪列,在创建表时由数据库自动生成,对于数据库中的每一行,RowID 伪列返回该行的地址。
RowID 应当具备以下特性:
1. 逻辑地标识每一行,且值唯一 |
2. 可以通过ROWID快速查询和修改表的其他列,自身不能被插入和修改 |
3. 用户可以控制是否开启此功能 |
2. 实现说明
在IvorySQL中系统列 ctid 字段代表了数据行在表中的物理位置,也就是行标识(tuple identifier),由一对数值组成(块编号和行索引),可以通过ctid快速的查找表中的数据行,这样和Oracle的RowID行为很相似,但是ctid值有可能会改变(例如当update/ vacuum full时),因此ctid不适合作为一个长期的行标识。
我们选择了表的oid加一个序列值组成的复合类型来作为RowID值,其中的序列是系统列。如果RowID功能被开启,则在建表的同时创建一个名为table-id_rowid_seq 的序列。同时在heap_form_tuple构造函数中,为 HeapTupleHeaderData 的长度增加8个字节,并标识td→t_infomask = HEAP_HASROWID 位来表示rowid的存在。
在开启了ROWID的GUC参数或建表时带上 WITH ROWID 选项,或对普通表执行 ALTER TABLE … SET WITH ROWID 时会通过增加序列创建命令来创建一个序列。
/*
* Build a CREATE SEQUENCE command to create the sequence object,
* and add it to the list of things to be done before this CREATE/ALTER TABLE
*/
seqstmt = makeNode(CreateSeqStmt);
seqstmt->with_rowid = true;
seqstmt->sequence = makeRangeVar(snamespace, sname, -1);
seqstmt->options = lcons(makeDefElem("as",
(Node *) makeTypeNameFromOid(INT8OID, -1),
-1),
seqstmt->options);
seqstmt->options = lcons(makeDefElem("nocache",
NULL,
-1),
seqstmt->options);
同时为了快速通过RowID伪列查询到一行数据,默认会在表的RowID列上创建一个UNIQUE索引,以提供快速查询功能。
RowID列作为系统属性列其实现是通过在 heap.c 中新增一个系统列来实现的。
/*
* Compatible Oracle ROWID pseudo column.
*/
static const FormData_pg_attribute a7 = {
.attname = {"rowid"},
.atttypid = ROWIDOID,
.attlen = -1,
.attnum = RowIdAttributeNumber,
.attcacheoff = -1,
.atttypmod = -1,
.attbyval = false,
.attalign = TYPALIGN_SHORT,
.attstorage = TYPSTORAGE_PLAIN,
.attnotnull = true,
.attislocal = true,
};
在pg_class系统表中增加一个 bool 类型的字段 relhasrowid,用于标识建表时的 WITH ROWID选项,如果建表时带了WITH ROWID选项,则 relhasrowid为 t,否则为f。 用户在执行 ALTER table … SET WITH ROWID/ WITHOUT ROWID 命令时,也会修改这个值。
/* T if we generate ROWIDs for rows of rel */
bool relhasrowid BKI_DEFAULT(f);
在RowID的存储方面,如果启用了RowID 伪列功能,则在插入表之前 heap_form_tuple函数会根据参数TupleDesc 中tdhasrowid 是否为true 在 HeapTupleHeaderData 中增加8个字节来存储序列值。 在heap_prepare_insert 函数中获取序列的nextval值,存在HeapTupleHeader 相应的位置。
if (relation->rd_rel->relhasrowid)
{
// Get the sequence next value
seqnum = nextval_internal(relation->rd_rowdSeqid, true);
// Set the HeapTupleHeader
HeapTupleSetRowId(tup, seqnum);
}