本文作者:张瑞远,现主要从事电信级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 sql1from (
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 sql1from (
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中对大表进行更新或删除操作的效率。希望本文能帮助大家在实际工作中更好地优化数据库操作,提升系统性能。