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

原理探究 -- 人大金仓 KingbaseES Ctid 与 Oracle Rowid 比较

原创 Kingbase研究院 2022-04-27
5876

熟悉Oracle的人都知道,Rowid 可用于加速数据访问。通常情况下,除非发生的跨分区的update操作,Rowid不会发生变化。KingbaseES 的 ctid 类似于Oracle Rowid,同样可以用于快速定位tuple位置。但由于自身KingbaseES MVCC实现机制的原因,ctid 在update操作时会发生变化,因此,使用Ctid 作为 Oracle Rowid 的替代方案不合适。而KingbaseES 的currtid 虽然可以满足Rowid 的功能的,但由于一致性机制的问题,使得通过currtid 取得记录的原始位置的方式存在性能问题。本文向大家介绍如何通过currtid 实现rowid 的功能,以及currtid 存在的具体问题。

一、Oracle Rowid

Oracle Rowid 记录了tuple的物理存储位置,通过Rowid 可以非常快速地访问tuple。因此,在极致性能的应用设计里,经常会使用到Rowid。典型的使用场景如下:

declare

        cursor cur01 as select rowid from tab1;

begin

        ......

        update tab1 where rowid='xxx';

end;

二、KingbaseES CTID

我们知道,对于Oracle ,一条tuple的Rowid 正常是不会变化的(引发row movement的操作除外,如:跨分区迁移update,表shrink),因此,应用设计上可以方便的使用rowid,加快访问速度。而对于KingbaseES,也有类似Oracle Rowid的Ctid,格式 “(blockid,slotid)”,同样记录了tuple存储的物理位置,通过Ctid也能快速的访问数据。但由于KingbaseES的多版本(MVCC)读实现机制的差异,Ctid会随update操作变化,这种情况下,使用Ctid有可能因为tuple被update,导致访问不到数据。为了让大家对于ctid有直观认识,举例如下:

A用户B用户
select ctid from t1 where id=1;返回 (0,1) 
 select ctid from t1 where id=1;返回 (0,1)
update t1 set name='aa' where ctid='(0,1)'; 
select ctid from t1 where id=1;返回 (0,2) 
 select * from t1 where ctid='(0,1)'; 无返回

可以看到,在有并发的情况下,用ctid访问是不可靠的。例子中,B用户再次通过Ctid 访问时,就会发现找不到数据。

三、currtid 函数

KingbaseES的update操作实际delete and insert 的结合体。对于update操作完成后,在vacuum 之前,原始tuple是包含指向新tuple的Ctid。函数 currtid 可以通过旧Ctid 取得updated 后的最新Ctid。具体见以下例子:

test=# insert into t1 values(1,'a');
INSERT 0 1
test=# select ctid from t1 where id=1;
 ctid  
-------
 (0,1)
(1 row)

test=# update t1 set name='aa' where id=1;
UPDATE 1
test=# select ctid from t1 where id=1;
 ctid  
-------
 (0,2)
(1 row)

test=# select * from t1 where ctid='(0,1)';
 id | name 
----+------
(0 rows)

test=# select currtid('t1'::regclass,'(0,1)');
 currtid 
---------
 (0,2)
(1 row)

test=# select * from t1 where ctid=currtid('t1'::regclass,'(0,1)');
 id |   name    
----+-----------
  1 | aa       
(1 row)

可以看到,通过将初始的 Ctid 传递给 currtid 函数,可以取得最新的 Ctid 。

PS:currtid 有效的前提是update 后,多版本信息没有被清理掉,也就是没有进行vacuum操作。

四、性能问题

从以上例子可以看到,使用currtid 可以部分避免期间数据被修改的问题而导致的通过Ctid无法读取到数据的问题。但实际上,这里有个性能的问题,用这种方式替代Oracle Rowid不合适。请看实际例子:

test=# explain select * from t1 where ctid=currtid('t1'::regclass,'(0,1)');
                       QUERY PLAN                       
--------------------------------------------------------
 Seq Scan on t1  (cost=0.00..26.95 rows=1 width=44)
   Filter: (ctid = currtid('16387'::oid, '(0,1)'::tid))
(2 rows)

test=# explain select * from t1 where ctid='(0,2)';
                    QUERY PLAN                     
---------------------------------------------------
 Tid Scan on t1  (cost=0.00..4.01 rows=1 width=44)
   TID Cond: (ctid = '(0,2)'::tid)
(2 rows)

可以看到,对于 ctid=currtid('t1'::regclass,'(0,1)') ,实际上采取的是 seqscan 。问题是currtid('t1'::regclass,'(0,1)') 是在等式右边的,不涉及 ctid 的转换,为什么无法使用 Tid Scan ? 

其实,这是由于函数的稳定态属性导致的。函数稳定态对于执行效率的影响,可以参照:https://www.modb.pro/db/624181

我们来看currtid 函数属性:

test=# select proname,provolatile from pg_proc where proname='currtid';
 proname | provolatile 
---------+-------------
 currtid | v

函数属性是 volatile ,volatile 函数导致无法使用TID scan

五、尝试修改函数属性为immutable

把函数的属性改成immutable 情况下的执行计划:

test=# update pg_proc set provolatile='i' where proname='currtid';
UPDATE 1
test=# explain select * from t1 where ctid=currtid('t1'::regclass,'(0,1)');
                    QUERY PLAN                     
---------------------------------------------------
 Tid Scan on t1  (cost=0.00..4.01 rows=1 width=44)
   TID Cond: (ctid = '(0,2)'::tid)
(2 rows)

可以看到,修改函数的属性为 immutable后,可以走 Tid Scan了。

六、volatile 函数与 immutable函数差异

就本例而言,对于SQL:select * from t1 where ctid=currtid('t1'::regclass,'(0,1)', '(0,1)' )。如果currtid是volatile 类型的函数,优化器采取 Seq Scan,针对每个tuple,都会执行一次函数调用。函数调用是在访问tuple之后,因此,能够保证数据的绝对准确性;如果currtid是immutable 类型的函数,我们知道immutable 类型的函数是在解析时执行。执行SQL时,先执行函数,再将结果以参数形式传给SQL。这里的风险点是,如果从函数调用开始到SQL执行完成之前,如果tuple被update,可能导致返回结果的不准确。幸运的是,无论函数调用,还是TID scan,都是非常快的(微秒级别),基本可以避免影响。

当然,如果一定要考虑结果的绝对准确,修改函数稳定态属性不是安全的方法。

PS:新版本的KingbaseES 兼容了Oracle的Rowid,相比于Oracle是虚列,KingbaseES 是隐含的实体列,并且在实体列上建了唯一索引。

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

评论