如何利用 rowid 在OceanBase 中处理大表时提效

本文作者:张瑞远,现主要从事电信级IT系统及数据库的规划设计、架构设计、运维实施、运维服务、故障处理、性能优化等工作,曾经从事银行、证券数仓设计、开发、优化类工作,持有Orale OCM,MySQL OCP及国产代表数据库认证。 获得包括 OceanBase OBCE、Oracle OCP 11g 等专业技能与认证。

前言:

了解数据库优化技巧的朋友应当知道,在Oracle数据库中,处理大表的更新或删除时,常常借助rowid切片,这一方法能有效减少undo,支持并行执行,降低IO负载,从而提升效率。

同样地,OceanBase也支持rowid切片,相关知识的详细介绍可参阅以下OceanBase知识库链接。

https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000000209970?back=kb

这个方法使用过程中也要注意一些事情,可以先了解下ob的rowid是怎么生成的,严军老师的博客有比较详细的介绍,链接如下

Oracle 和 OceanBase中ROWID的区别

所以如果更新字段中包括主键的话,通过rowid切片然后去批量更新的话,可能会产生冲突和部分切片的数据变化。

案例:

注意事项

1.更新列尽量不包括主键

2.切片大小尽量在2W-10W左右

3.更新尽量减少涉及分区(如果并发高涉及分区多,3.x很容易触发4023的报错,虽然是等待clog滑动窗口不是直接报错,但是效率会变的很差)

4.对于无法确定分区拆分的场景,减少并发(限制并发和每个切片的数据量也可以避免4013内存不足的发生)

5.表上的触发器尽量关闭,复杂和大量的触发器对于效率影响很大

比如我们需要更新USERCS.TABLECS 的col1字段,并且确定该表无其他业务操作,而且数据基本分布在666,777的分区里,那么shell脚本如下

复制代码
#!/bin/sh
export ip_a=xxx.xx.xx.xx            --数据库ip
export tenant_a=obcstenant          --数据库业务租户
export colony_a=obcscluster         --数据库集群
export colony_pw='xadsadqwdasf'     --数据库密码
echo "#### BEGIN ALTER PASSWD AND TBALEMODE  `date +%Y%m%d%H%M%S`####"
obclient -h${ip_a} -P2883 -usys@${tenant_a}#${colony_a} -p${colony_pw} -c <<EOF 
alter user USERCS identified by USERCS123;                            --因为测试环境为了避免影响以及方便添加了修改密码的操作
EOF
echo "#### END ALTER PASSWD AND TBALEMODE  `date +%Y%m%d%H%M%S`####"


echo "#### CREATE ABOUT TRIGGER SQLFILE `date +%Y%m%d%H%M%S`####"     --关闭和打开触发器的sql语句
> /usr/syncscript/custcs/disable_trigger.sql
> /usr/syncscript/custcs/enable_trigger.sql
obclient -h${ip_a} -P2883 -usys@${tenant_a}#${colony_a} -p${colony_pw} -c  -N <<!|tee >> /usr/syncscript/custcs/disable_trigger.sql
select  'alter trigger  '||OWNER||'.'||TRIGGER_NAME||' DISABLE;'  from  dba_triggers where TABLE_OWNER='USERCS' and TABLE_NAME in ('TABLECS')  and  STATUS='ENABLED';
!
obclient -h${ip_a} -P2883 -usys@${tenant_a}#${colony_a} -p${colony_pw} -c  -N <<!|tee >> /usr/syncscript/custcs/enable_trigger.sql
select  'alter trigger  '||OWNER||'.'||TRIGGER_NAME||' enable;'  from  dba_triggers where TABLE_OWNER='USERCS' and TABLE_NAME in ('TABLECS')  and  STATUS='ENABLED';
!
echo "#### END ABOUT TRIGGER SQLFILE `date +%Y%m%d%H%M%S`####"


echo "#### BEGIN DISABLE TRIGGER `date +%Y%m%d%H%M%S`####"               --关闭触发器
obclient -h${ip_a} -P2883 -usys@${tenant_a}#${colony_a} -p${colony_pw} -c <<EOF 
source /usr/syncscript/custcs/disable_trigger.sql
EOF
echo "#### END DISABLE TRIGGER `date +%Y%m%d%H%M%S`####"

echo "#### BEGIN CREATE TABLECS SQLFILE `date +%Y%m%d%H%M%S`####"         --生成USERCS.TABLECS根据rowid切片的sql
export des_table=USERCS.TABLECS 
export des_file=TABLECS
export des_count=1000                                                     --整个表切成1000份, 可以先count一下表看下数据量,确保每个分片不会太大不会太小,group分区键看下数据分布
export des_sh_name=subscssss                                              --生成批量脚本的前缀名称
>/usr/syncscript/custcs/${des_file}.sql
obclient -h${ip_a} -P2883 -uUSERCS@${tenant_a}#${colony_a} -p'USERCS123' -c  -N <<!|tee >> /usr/syncscript/custcs/${des_file}.sql     --生成USERCS.TABLECS根据rowid切片的sql
with a as
(select  str as ob_id,rownum rn from  (select ob_rowid,decode(mod(rownum,trunc((select count(*) from ${des_table} where partition_id in (666,777) )/${des_count})),0,''||rowid,'') str,rownum seq from (select  rowid ob_rowid from ${des_table} where partition_id in (666,777) order by 1 )) where str is not null order by seq)
select  'update  USERCS.TABLECS t  set COL1=''xxxxxxxxxxx''  where partition_id in (666,777) and rowid '||case when  rc=0 then '< '''||OB_ID1||'''; commit;'  when rc is null then '>= '''||OB_ID1||'''; commit;' else '>= '''||OB_ID1||''' and rowid<'''||ob_id2||'''; commit;' end as  sql1
 from  (
select ob_id ob_id1,'' ob_id2,rn,0 as rc  from  a where rn=1 union all
select a1.ob_id ob_id1,a2.ob_id,a1.rn rownum1,a2.rn rownum2   from  a a1 left join a a2 on a1.rn=a2.rn-1) order by rn;
!

export cnt3=`cat /usr/syncscript/custcs/${des_file}.sql|wc -l`    --统计生成的sql数量
export des_ma=`expr $cnt3 / 13`                                   --拆分为13份脚本

echo "#### BEGIN  NO666,777 TABLECS DATA  `date +%Y%m%d%H%M%S`####"       --先确认了非666,777分区的数据很少,可以单独更新
obclient -h${ip_a} -P2883 -uUSERCS@${tenant_a}#${colony_a} -p'USERCS123' -c <<EOF 
update  USERCS.TABLECS t  set COL1=''xxxxxxxxxxx'' where  partition_id not  in (666,777);
EOF
echo "#### END  NO666,777 TABLECS DATA  `date +%Y%m%d%H%M%S`####"

split -d -l ${des_ma} ${des_file}.sql   ${des_sh_name} &&ls|grep ${des_sh_name}|xargs -i mv {} {}.sh        --拆分rowid切分的sql文件成13个脚本
ls|grep ${des_sh_name}|xargs -i sed -i "1iobclient -h${ip_a} -P2883 -uUSERCS@${tenant_a}#${colony_a} -p'USERCS123' -c <<EOF " {}
ls|grep ${des_sh_name}|xargs -i sed -i "1i#!/bin/sh"  {}
find . -name  "${des_sh_name}*"|xargs -i sed  -i '$a\EOF'  {}
find . -name  "${des_sh_name}*"|xargs -i sed  -i '$a\echo "####{} END `date +%Y%m%d%H%M%S`"'  {}
echo "#### END CREATE SQLFILE `date +%Y%m%d%H%M%S`####"
echo "#### BEGIN RUN DESEN `date +%Y%m%d%H%M%S`####"
export cn=`ls|grep ${des_sh_name}|wc -l`                            --并发度控制,因为这里拆分的脚本只有13个,所以有多少脚本设置的多少并发,如果单分区大表或者无法按照分区拆分的大表可以多分一些切片,多分些脚本,cn并发写成固定的值比如5
ls|grep ${des_sh_name}|xargs -i -P ${cn} sh {} &                    --并发执行生成的这些脚本
wait
rm -rf /usr/syncscript/custcs/${des_sh_name}*
echo "#### END RUN TABLECS DESEN `date +%Y%m%d%H%M%S`####"


echo "#### BEGIN ENABLE TRIGGER `date +%Y%m%d%H%M%S`####"           --打开触发器   
obclient -h${ip_a} -P2883 -usys@${tenant_a}#${colony_a} -p${colony_pw} -c <<EOF 
source /usr/syncscript/custcs/enable_trigger.sql
EOF
echo "#### END ENABLE TRIGGER `date +%Y%m%d%H%M%S`####"

上面写了如果单分区大表或者无法按照分区拆分的大表可以多分一些切片,多分些脚本,cn并发写成固定的值比如5,案例如下

复制代码
echo "#### BEGIN CREATE TABLECS2 SQLFILE `date +%Y%m%d%H%M%S`####"
export des_table=USERCS1.TABLECS2 
export des_file=TABLECS2
export des_count=2000                             --按照rowid切分为2000份
export des_sh_name=tbsfcssss
>/usr/syncscript/custcs/${des_file}.sql
obclient -h${ip_a} -P2883 -uUSERCS1@${tenant_a}#${colony_a} -p'USERCS1123' -c  -N <<!|tee >> /usr/syncscript/custcs/${des_file}.sql
with a as
(select  str as ob_id,rownum rn from  (select ob_rowid,decode(mod(rownum,trunc((select count(*) from ${des_table} )/${des_count})),0,''||rowid,'') str,rownum seq from (select  rowid ob_rowid from ${des_table}  order by 1 )) where str is not null order by seq)
select  'update  USERCS1.TABLECS2 t  set  COL2=''ASDASD'' where rowid '||case when  rc=0 then '< '''||OB_ID1||'''; commit;'  when rc is null then '>= '''||OB_ID1||'''; commit;' else '>= '''||OB_ID1||''' and rowid<'''||ob_id2||'''; commit;' end as  sql1
 from  (
select ob_id ob_id1,'' ob_id2,rn,0 as rc  from  a where rn=1 union all
select a1.ob_id ob_id1,a2.ob_id,a1.rn rownum1,a2.rn rownum2   from  a a1 left join a a2 on a1.rn=a2.rn-1) order by rn;
!

export cnt3=`cat /usr/syncscript/custcs/${des_file}.sql|wc -l`
export des_ma=`expr $cnt3 / 200`                            -- 切分成200个脚本          

split -d -l ${des_ma} ${des_file}.sql   ${des_sh_name} &&ls|grep ${des_sh_name}|xargs -i mv {} {}.sh 
ls|grep ${des_sh_name}|xargs -i sed -i "1iobclient -h${ip_a} -P2883 -uUSERCS1@${tenant_a}#${colony_a} -p'USERCS1123' -c <<EOF " {}
ls|grep ${des_sh_name}|xargs -i sed -i "1i#!/bin/sh"  {}
find . -name  "${des_sh_name}*"|xargs -i sed  -i '$a\EOF'  {}
find . -name  "${des_sh_name}*"|xargs -i sed  -i '$a\echo "####{} END `date +%Y%m%d%H%M%S`"'  {}
echo "#### END CREATE SQLFILE `date +%Y%m%d%H%M%S`####"
echo "#### BEGIN RUN DESEN `date +%Y%m%d%H%M%S`####"
export cn=5                                                 --按照并发5去执行切分后的脚本
ls|grep ${des_sh_name}|xargs -i -P ${cn} sh {} &
wait
rm -rf /usr/syncscript/custcs/${des_sh_name}*
echo "#### END RUN TABLECS2 DESEN `date +%Y%m%d%H%M%S`####"

总结:

通过合理利用rowid切片技术,可以显著提高在OceanBase中对大表进行更新或删除操作的效率。希望本文能帮助大家在实际工作中更好地优化数据库操作,提升系统性能。

相关推荐
赵渝强老师1 天前
【赵渝强老师】OceanBase租户的资源管理
数据库·mysql·oracle·oceanbase
练习时长两年半的程序员小胡1 天前
JVM 性能调优实战:让系统性能 “飞” 起来的核心策略
java·jvm·性能调优·jvm调优
GottdesKrieges6 天前
obd运维OceanBase数据库的常见场景
运维·数据库·oceanbase
OceanBase数据库官方博客7 天前
伯俊科技× OB Cloud:零售业落地AI的“三步走”渐进式发展实践
人工智能·oceanbase·分布式数据库
颜挺锐8 天前
在 OceanBase 中,使用 TO_CHAR 函数 直接转换日期格式,简洁高效的解决方案
数据库·oceanbase
躲在没风的地方9 天前
OceanBase数据库
linux·数据库·oceanbase
OceanBase数据库官方博客9 天前
如何使用 minio 完成OceanBase社区版的归档和备份
oceanbase·分布式数据库·备份恢复
OceanBase数据库官方博客9 天前
OceanBase 混合检索解读:向量+标量,应该优先查哪个
ai·oceanbase·分布式数据库·向量检索·混合检索
nangonghen9 天前
oceanbase oms工具实时迁移oceanbase至mysql
mysql·oceanbase·oms