oracle高级复制
首先,数据库要具备高级复制功能(用system身份登录数据库,查看v$option视图,如果其中Advanced replication为TRUE,则支持高级复制功能;否则不支持)
一.数据库基本情况
数据库A 版本oracle 10.2.0.3 数据库名sid:shenzhen
数据库B 版本oracle 10.2.0.3 数据库名sid:beijing
主体定义站点:A shenzhen主体站点:B shenzhen注:主体定义站点指配置复制工作的站点
本例涉及的用户. 复制管理员:repadmin 应用用户:cqm本例复制的对象:reptest 数据表 本例的先决条件:你需要设置好相应的参数,job_queue_processes需要大于0,global_name=true,并且建立相应的db link.
alter system set global_names=true scope=both;
二.在两个数据库上分别创建应用用户CQM
CREATE USER CQM IDENTIFIED BY CQM DEFAULT TABLESPACE users TEMPORARY TABLESPACE TEMP;
GRANT DBA TO CQM;
三.在两个数据库上分别创建复制管事员用户REPADMIN
--创建repadmin用户管理复制环境
CREATE USER REPADMIN IDENTIFIED BY REPADMIN;
ALTER USER REPADMIN DEFAULT TABLESPACE USERS;
ALTER USER REPADMIN TEMPORARY TABLESPACE TEMP;
GRANT connect, resource TO REPADMIN;
--授予repadmin用户权限可以管理当前站点中任何主体组
EXECUTE dbms_repcat_admin.grant_admin_any_schema('REPADMIN');
--授予repadmin用户权限可以为任何表创建snapshot logs
GRANT comment any table TO REPADMIN;
GRANT lock any table TO REPADMIN;
--指定repadmin用户为propagator,并授予执行任何procedure的权限
EXECUTE dbms_defer_sys.register_propagator('REPADMIN');
GRANT execute any procedure TO REPADMIN;
四.更改两个数据库的全局名称
alter database rename global_name to beijing.TEST.COM.CN;
alter database rename global_name to shenzhen.TEST.COM.CN;
五.在两个数据库上建立数据库链接
create public database link beijing.TEST.COM.CN connect to REPADMIN identified by REPADMIN using 'beijing';
create public database link shenzhen.TEST.COM.CN connect to REPADMIN identified by REPADMIN using 'shenzhen';
六.在两个数据库的应用用户CQM下创建表
在数据库shenzhen上用户CQM下:注意,要进行复制的表必须有主键
CREATE TABLE TEST(ID NUMBER(10),TIMESTAMP DATE DEFAULT sysdate);
ALTER TABLE TEST ADD(CONSTRAINT TEST_PK PRIMARY KEY(ID));
在数据库beijing上用户CQM下:
CREATE TABLE TEST(ID NUMBER(10),TIMESTAMP DATE DEFAULT sysdate);
ALTER TABLE TEST ADD(CONSTRAINT TEST_PK PRIMARY KEY(ID));
七.在主体定义站点开始操作(数据库A:shenzhen)
以REPADMIN登录数据库shenzhen
创建复制组:
execute dbms_repcat.create_master_repgroup('rep_hh'); [多余的]
execute dbms_repcat.create_master_repgroup('repg');
在复制组里加入复制对象:
execute dbms_repcat.create_master_repobject(sname=>'cqm',oname=>'test',type=>'table',use_existing_object=>true,gname=>'repg',copy_rows=>false);
对复制对象产生复制支持:
execute dbms_repcat.generate_replication_support('cqm','test','table');
execute dbms_repcat.drop_master_repobject ('scott','dept','table');[取消同步]
添加主体复制节点:
execute dbms_repcat.add_master_database(gname=>'repg',master=>'beijing.test.com.cn',use_existing_objects=>true,copy_rows=>false, propagation_mode => 'asynchronous');
********************************************** 参数说明: gname 主复制组名 master 加入主复制节点的另一个数据库 use_existing_object true表示用主复制节点已经存在的数据库对象 copy_rows false表示第一次开始复制时不用和主复制节点保持一致 propagation_mode 同步地执行 synchronous(上边配置异步复制) ***********************************************
在主体定义站点启动复制:
execute dbms_repcat.resume_master_activity('repg',true);
execute dbms_repcat.resume_master_activity('repg',false);
execute dbms_repcat.suspend_master_activity('repg')[停止复制]
八.至此配置完成
附:
使用异步复制要运行以下的过程!
exec dbms_defer_sys.schedule_push (destination => 'beijing.test.com.cn',interval => 'sysdate + 1/1440',next_date => sysdate);
exec dbms_defer_sys.schedule_purge (next_date => sysdate,interval => 'sysdate + 1/1440',delay_seconds => 0,rollback_segment => '');
***********************************************
exec dbms_defer_sys.schedule_push (destination => 'shenzhen.test.com.cn',interval => 'sysdate + 1/1440',next_date => sysdate);
exec dbms_defer_sys.schedule_purge (next_date => sysdate,interval => 'sysdate + 1/1440',delay_seconds => 0,rollback_segment => '');
这种配置方式对网络的影响比较小!管理也比较简单
1)模拟小数据量测试:OK
2)模拟大数据量测试:OK
CREATE OR REPLACE procedure insert_into_test
as
i number;
m NUMBER;
n NUMBER;
BEGIN
n:=0;
FOR i IN 1..10000 LOOP
m:=i;
INSERT INTO test(id)VALUES (m);
n:=n+1;
IF n=1000 THEN
COMMIT;
n:=0;
END IF;
END LOOP;
COMMIT;
END;
添加复制对象的四个步骤!
execute dbms_repcat.suspend_master_activity('repg')
execute dbms_repcat.create_master_repobject(sname=>'cqm',oname=>'test1',type=>'table',use_existing_object=>true,gname=>'repg',copy_rows=>false);
execute dbms_repcat.generate_replication_support('cqm','test1','table');
execute dbms_repcat.resume_master_activity('repg',false)
execute dbms_repcat.resume_master_activity('repg',true)
附:
做完的时候开始插入数据有问题
SQL> insert into test(id) values(1);
insert into test(id) values(1)
*
ERROR at line 1:
ORA-04067: not executed, stored procedure "CQM.TEST$RP" does not exist
ORA-01085: preceding errors in deferred rpc to "CQM.TEST$RP.REP_INSERT"
ORA-02063: preceding 2 lines from BEIJING
后来就可以拉
1, 创建前数据要统一
2, 运行execute dbms_repcat.suspend_master_activity('repg')后源数据库的表不可以再进行数据的插入!
3, Job 可以停到 并不影响数据的复制(表怀疑与搞笑)
4, 现在是同样的操作系统同样的数据库版本
5, 以上是在同一操作系统下测试AIX5207
6, 高级复制如果目的站点有问题或者网络有问题!源站点的数据操作将会出错!
7, 如果数据不同步的情况下,对源站点的数据操作会产生ORA-01403: no data found的问题!
8, 以上是数据的传输方式为同步的,如果修改成异步的传输方式上边的问题将不会出现的!
9, 不可以使用truncate table 清理数据
常用问题解答:
Q:如何计算延迟事务将占用多少的资源?
A:Deferred Transactions
Oracle forwards data replication information by propagating (that is, sending and executing) the RPCs that are generated by the internal triggers described previously. These RPCs are stored in the deferred transaction queue. In addition to containing the execution command for the internal procedure at the destination site, each RPC also contains the data to be replicated to the target site. Oracle uses distributed transaction protocols to protect global database integrity automatically and ensure data survivability.
Deferred Transaction Queue
This queue stores the transactions (for example, DML) that are bound for another destination in the master group. Oracle stores RPCs produced by the internal triggers in the deferred transaction queue of a site for later propagation. Oracle also records information about initiating transactions so that all RPCs from a transaction can be propagated and applied remotely as a transaction. Oracle's replication facility implements the deferred transaction queue using Oracle's advanced queuing mechanism.上面是Oracle联机文档中对于延迟事务和延迟事务队列的描述。开始的时候一直以为延迟事务应该是存在Oracle的一个内存结构中,所以总是担心如果复制环境中的网络长时间出现问题,那么会不会导致延迟事务队列占用大量的内存而使数据库的其它操作变慢,或者说超出了延迟事务可以使用的内存大小而产生错误。因为上面提到延迟事务队列使用的是Oracle的高级队列(Advanced Queue)算法,所以又查找了高级队列的文档,发现多处提到Table这个词,所以忽然明白所谓延迟事务的队列应该是存储在磁盘上的某些表中,这样陡然就解决了心中很多疑问,首先事务多只是占用硬盘空间,其次要想计算事务占用的资源可以通过表的block数来计算。于是通过SQL Trace,找到了延迟事务相关视图的基表。
deftran对应DEF$_AQCALL表,通过执行计划也发现在统计大量延迟事务总数时候速度极为缓慢的原因,因为在作TABLE ACCESS FULL DEF$_AQCALL,同时还会作TABLE ACCESS FULL DEF$_AQERROR,还有UNION ALL的操作。
deferror对应DEF$_ERROR表。defcall对应的也是DEF$_AQCALL和DEF$_AQERROR表。现在我们检查一下DEF$_AQCALL表的信息。
SQL>; col owner for a10
SQL>; col object_name for a20
SQL>; select owner,object_name,object_id,data_object_id,object_type from dba_objects where object_name='DEF$_AQCALL';
OWNER OBJECT_NAME OBJECT_ID DATA_OBJECT_ID OBJECT_TYPE
---------- -------------------- ---------- -------------- ------------------
SYS DEF$_AQCALL 3913 SYNONYM
SYSTEM DEF$_AQCALL 3861 3861 TABLE
SYSTEM DEF$_AQCALL 3869 QUEUE
Executed in 0.06 seconds
从上面的结果可以看到这个表是属于SYSTEM的,在SYS下有一个同义词。再检查一下segment的情况,我们可以从dba_segments或者dba_extents视图中查看。
SQL>; col tablespace_name for a20
SQL>; select owner,tablespace_name,bytes,blocks from dba_segments where segment_name='DEF$_AQCALL';
OWNER TABLESPACE_NAME BYTES BLOCKS
---------- -------------------- ---------- ----------
SYSTEM SYSTEM 65536 8
Executed in 0.11 seconds
由此我们已经可以知道作为高级复制中延迟事务存储所占用的资源,同时由于这是普通的表,那么当插入记录的时候当然也是会缓存在buffer cache中。这里不作讨论。也不再讨论DEF$_AQERROR表,因为只有在延迟事务产生错误时才会插入,如果高级复制环境中没有太多错误,这个表的资源占用可以不考虑。
为了继续验证,现在测试环境中有REP_HOME复制组,其中的复制对象是SCOTT.EMP_2003表。断开网络连接的情况下,我们插入10000条记录。再次检索DEF$_AQCALL表现在的情况。
SQL>; select owner,tablespace_name,bytes,blocks from dba_segments where segment_name='DEF$_AQCALL';
OWNER TABLESPACE_NAME BYTES BLOCKS
---------- -------------------- ---------- ----------
SYSTEM SYSTEM 2097152 256
Executed in 0.08 seconds
现在该表的大小已经扩大到2M,可以认为所有的复制数据和队列信息都存储在这张表中。
SQL>; select count(*) from DEF$_AQCALL;
COUNT(*)
----------
10000
Executed in 0.04 seconds
再次证明确实是10000条数据。
如果此时我们用repadmin用户检索defcall和deftran视图
SQL>; select count(*) from defcall;
COUNT(*)
----------
10000
已用时间: 00: 00: 02.04
SQL>; select count(*) from deftran;
COUNT(*)
----------
1
已用时间: 00: 00: 00.00
可以看到deftran视图中只有一条记录,因为上面的10000条数据的插入是一个事务中完成的,所以在复制环境中作为一个延迟事务处理。而defcall中则是10000条记录,详细查看内容,知道所有的cal事务号都相同,而callno不同,同时我们发现直接检索DEF$_AQCALL只需要0.04秒,而检索defcall视图却需要2秒,所以如果想要计算到底有多少数据需要处理的时候,我们可以直接从DEF$_AQCALL检索,这样可以缩短查询时间。
Q:如果一张表没有主键,又确实需要复制,怎么办?
A:不建议在高级复制的环境中出现这样的情况,应该确保每张表都有主键。如果确实存在这种情况,那么需要用DBMS_REPCAT.SET_COLUMNS来生成代用主键。
execute DBMS_REPCAT.SET_COLUMNS(sname =>; test', oname =>; 'tabel', column_list =>; 'col1,col2,col3,col4');其中column_list是用逗号隔开的字段列表,不能有空格。注意:不要在执行set_columns之前生成对于没有主键的对象的复制支持,也就是不要运行generate_replication_support,否则会导致all_repobject视图中该对象状态变为ERROR,而无法再次set_columns。如果误运行了复制支持而又没有成功,那么需要删除掉这个复制对象再重新生成。也就是在对没有主键的表生成复制的时候,必须遵循以下顺序:
create_master_repobject ->; set_columns ->; generate_replication_support
Q:高级复制环境中出现长时间的网络问题会出现什么情况?
A:由于高级复制的传播都是通过JOB来实现的,而大家知道Oracle对于JOB的执行有个限制,就是如果一个JOB执行失败了16次,那么这个JOB将会被标志为BROKEN,以后这个JOB再也不会被自动执行,除非是手动设置BROKEN为FALSE或者手动成功地运行一次JOB。这个特性给我们的实际应用中带来了一些麻烦,假设我们的PUSH JOB定义的时间间隔是一分钟,那么如果主体站点之间的网络出现长时间的问题,比如说超过了16分钟,也就是此时JOB已经失败了16次,那么PUSH的JOB就被标志为BROKEN了,这样等到网络问题修复,会发现堆积的延迟事务也不会被PUSH到其它的主体站点上。如果不注意这个问题,往往就会出现严重的问题。解决方案是另外作一个JOB,这个JOB里面每隔一定时间自动检查那个PUSH JOB的状态,如果是BROKEN的,那么自动将其BROKEN状态重新设置为FALSE,这样下次又可以重新执行了。这个JOB中执行的存储过程基本上如下:
DECLARE
CURSOR my_broken_jobs IS
SELECT job FROM user_jobs WHERE broken = 'Y';
BEGIN
FOR broken_job IN my_broken_jobs LOOP
BEGIN
dbms_job.broken(broken_job.job, FALSE);
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
END LOOP;
END;
Q:如果高级复制环境中的主体定义站点损坏,如何将主体定义站点切换到另外的主体站点上?
A:分为两种情况。备注:每次运行完repcat包以后都应该执行一次commit,因为某些rep的存储过程是不会自动commit的,同时这也是一个troubleshooting,一般的rep脚本都会较快的返回结果,如果一条命令之后长时间没有结果返回,那么很可能是上面的命令没有commit,取消掉当前的命令,然后作一次commit,再重新执行,一般都能够解决问题。一是只有主体定义站点损坏。假设站点A是主体定义站点,已经损坏,在复制环境中还有站点B,想作为新的主体定义站点。
1.以repadmin身份登录站点B,执行主体站点切换。
connect repadmin/repadmin
execute dbms_repcat.relocate_masterdef
(gname =>'repg',
old_masterdef =>'shenzhen.test.com.cn',
new_masterdef =>'beijing.test.com.cn',
notify_masters =>true,
include_old_masterdef =>false);
2.将站点A作为主体站点删除
execute dbms_repcat.remove_master_databases
(gname =>'repg',
master_list =>'shenzhen.test.com.cn');
3.当站点A重新可用时,用repadmin用户登录站点A,删除其中的复制组信息
connect repadmin/repadmin
execute dbms_repcat.drop_master_repgroup
(gname =>'repg',
drop_contents =>true,
all_sites => false);
如果要使站点A重新称为复制环境中的一个主体站点,继续执行下面的4,5两步,否则切换主体定义站点就已经完成了。
4. 登录站点B(新的主体定义站点)
connect repadmin/repadmin
execute dbms_repcat.suspend_master_activity
(gname =>'repg')
execute dbms_repcat.add_master_database
(gname => 'repg',
master =>'shenzhen.test.com.cn',
use_existing_objects =>true,
copy_rows =>false);
5.重新开始复制
execute dbms_repcat.resume_master_activity
(gname =>'repg',true)
第二种情况是一些主体站点和主体定义站点同时损坏了。
1.依次登录所有正常运行的主体站点,执行主体定义站点切换
execute dbms_repcat.relocate_masterdef
(sname =>; 'schemaname',
old_masterdef =>; 'oldmaster.world',
new_masterdef =>; 'newmaster.world',
notify_masters =>; false, /*此处是false,而第一种情况中这个参数是true */
include_old_masterdef =>; false);
后面的操作步骤跟情况一相同,依次执行2-5就可以了。
Q:如何检查一个错误的延迟事务到底作了什么?
A:有些时候因为种种原因,可能复制操作会出现错误。检查DEFERROR视图会发现有错误的延迟事务,我们首先可以通过DEFERRED_TRAN_ID和CALLNO在DEFCALL视图中查询这个操作大体上是在干什么。
SQL>; select schemaname, packagename, procname from defcall;
SCHEMANAME PACKAGENAME PROCNAME
------------------------------ ------------------------------ ------------------------------
SCOTT EMP_2003$RP REP_DELETE
SCOTT EMP_2003$RP REP_DELETE
Executed in 0.06 seconds
其中SCHEMANAME表示哪个用户执行的DML,PACKAGENAME中可以知道是哪个对象上的操作,PROCNAME则可以知道是插入,更新还是删除。但是这样的信息往往不足够我们判断问题。我们想知道到底这个SQL干了什么,怎么办?我们可以利用DBMS_DEFER_QUERY.GET_CALL_ARGS来达到目的。运行以下的存储过程,可以得到想要查询的延迟事务的具体参数值。
CREATE OR REPLACE PROCEDURE GET_CALL(tran_id IN STRING,
callno IN NUMBER,
startarg IN NUMBER := 1,
argcnt IN NUMBER,
argsize IN NUMBER,
tran_db IN STRING := '') IS
v_types DBMS_DEFER_QUERY.TYPE_ARY;
v_vals DBMS_DEFER_QUERY.VAL_ARY;
indx NUMBER;
v_type_desc VARCHAR2(50);
BEGIN
DBMS_DEFER_QUERY.GET_CALL_ARGS(callno =>; callno,
startarg =>; startarg,
argcnt =>; argcnt,
argsize =>; argsize,
tran_db =>; tran_db,
tran_id =>; tran_id,
date_fmt =>; 'YYYY-MM-DD HH24:MI:SS',
types =>; v_types,
vals =>; v_vals);
FOR indx IN 1 .. argcnt LOOP
IF v_types(indx) = 1 THEN
v_type_desc := 'VARCHAR2';
END IF;
IF v_types(indx) = 2 THEN
v_type_desc := 'NUMBER';
END IF;
IF v_types(indx) = 12 THEN
v_type_desc := 'DATE';
END IF;
IF v_types(indx) = 23 THEN
v_type_desc := 'RAW';
END IF;
IF v_types(indx) = 96 THEN
v_type_desc := 'CHAR';
END IF;
IF v_types(indx) = 11 THEN
v_type_desc := 'ROWID';
END IF;
dbms_output.put_line('Arg ' || indx || ': Datatype ' ||
v_type_desc || '; Value: ' ||
v_vals(indx));
END LOOP;
END GET_CALL;
另外我们可以通过查询DEF$_AQCALL 表的USER_DATA字段也可以得到数据,这是一个BLOB字段,提取BLOB字段内容的方法这里不再讨论,如果有兴趣的可以自己试一下