Here is the big picture for this chapter. We will create a new user and tablespace to isolate all the examples used in this chapter. That user's schema will contain one table replicated between STRM1
and STRM2
database. Both STRM1
and STRM2
are sources. STRM1
is the source for STRM2
and vice-versa. The Streams Administrator will configure the Streams environment. Conflict resolution will handle the situation where the exact same data inputted on one or more sources at the same time. The conflict resolution will use the built-in MAXIMUM confliction resolution handler.
The set-up table is given as follows:
Description |
Value |
---|---|
New User |
LEARNING |
Table |
EMPLOYEE |
New Tablespace |
LEARNING |
Replication Type |
N-Way |
Conflict Resolution |
MAXIMUM |
Streams Administrator |
STRM_ADMIN on both STRM1 & STRM2 |
Databases |
STRM1 & STRM2 |
This chapter is heavy on the PL/SQL and does not use DB Control or Grid Control to set up Streams. As such, this is a deeper dive into what actually is going on under the covers. There is an extensive use of comments in the provided code and images to help you understand and visualize what Streams is doing. The set-up here is to make sure you learn, understand, and are able to implement N-way replication by providing a working example that is extendable.
All code in this chapter ran against a Beta of Oracle 11g R2 on both Windows and Linux 32 bit. The authors have made efforts to make the code reusable with slight modification.
Please refer to Chapter 3, Prepare the Rafts and Secure Your Gear: The pre-work before configuring Oracle 11g Streams, for a full explanation on how to configure the database for Streams. For this chapter, the preliminary setup involves:
On both STRM1
and STRM2:
ADD SUPPLEMENTAL LOG
strm_admin
using a DBA account. TNSNAMES.ORA
tnsnames.ora
file on the STRM1
host should have an STRM2
entrytnsnames.ora
file on the STRM2
host should have an STRM1
entry global_names
is set to TRUE
on both databases.select name, value from v$parameter where name = 'global_names';
select * from global_name;
STRM_ADMIN
Scripts_5_1_PSU.sql
provides the setup for the above. The script does not include the steps:
TNSNAMES.ORA
tnsnames.ora
file on the STRM1
host should have an STRM2
entry tnsnames.ora
file on the STRM2
host should have an STRM1
entry global_names
is set to TRUE
on both databases.select name, value from v$parameter where name = 'global_names';
select * from global_name;
STRM_ADMIN
Carefully review Scripts_5_1_PSU.sql
and modify what you deem appropriate. Then use an account with DBA privileges to run Scripts_5_1_PSU.sql
script. At the minimum, you will need to modify the creation of the tablespaces.
At this point, both STRM1
and STRM2
are set up according to the setup table with the exception of the implementation of conflict resolution. Now, we will configure Streams first on STRM1
then on STRM2
. Check scripts will also be run to confirm that the configuration of Streams is going well.
In a Streams environment, you connect to more than one database at a time while performing Streams administration. To make things easier, we highly suggest changing your glogin.sql
script located in your $ORACLE_HOME/SQLPLUS/admin
. The following code will change the prompt from the default SQL>
to the USER@GLOBAL_NAME>
. In our case we will see:
when we are logged into STRM1
and STRM2
as STRM_ADMIN
respectively.
When logged into the database as DBA, the login prompt will reflect
showing DBA1 being logged into STRM1
and STRM2
respectively.
----------------------------------------------------------- -- start: change the sql prompt -- to reflect user@global_name -- Code for modifying glogin.sql -- Append to the end of the glogin.sql set termout off col gname new_value prompt_gname select global_name gname from global_name; set sqlprompt "&&_USER'@'prompt_gname> " set termout on -- end: change the sql prompt -----------------------------------------------------------
STRM_ADMIN
is used to run all of the scripts to set up for Streams. If necessary, a DBA account may be used. The login prompt will reflect the logged-in user and at what database. STRM_ADMIN
does have DBA role and privileges, but we suggest opening a separate session and using a different user for simplicity. The code for the next two sections is in the files Scripts_5_1_STRM1_STRM2.sql
and Scripts_5_1_STRM2_STRM1.sql
.
If you are reusing the Streams set up from Chapter 4, Single-Source Configuration, please run the following to clear out all previous configurations. This will destroy the previous configuration! If you are starting with a new STRM_ADMIN
account with no previous configuration, skip this step.
[email protected]> EXEC DBMS_STREAMS_ADM.REMOVE_STREAMS_CONFIGURATION();
Let us step back and plan the set up of Streams between STRM1
and STRM2
.
On STRM1
, log in as STRM_ADMIN
-- ADD THE QUEUE: A good queue name is STREAMS_CAPTURE_Q
-- ADD THE CAPTURE RULE
-- ADD THE PROPAGATION RULE
-- INSTANTIATE TABLE ACROSS DBLINK
-- DBLINK TO DESTINATION is STRM2.US.APGTECH.COM
-- SOURCE is STRM1.US.APGTECH.COM
On STRM2
log in as STRM_ADMIN
-- ADD THE QUEUE: A good queue name is STREAMS_APPLY_Q
-- ADD THE APPLY RULE
Start everything up and test the Stream on STRM1
Then, check to see if the record is Stream'ed to STRM2
.
We take the additional step of performing a commit after each running of a procedure. Although not technically needed, we use it as a precaution.
-- On STRM1
, log in as STRM_ADMIN
-- ADD THE QUEUE: A good queue name is STREAMS_CAPTURE_Q
-- [email protected]> BEGIN DBMS_STREAMS_ADM.SET_UP_QUEUE( queue_table => '"STREAMS_CAPTURE_QT"', queue_name => '"STREAMS_CAPTURE_Q"', queue_user => '"STRM_ADMIN"'), END; / commit; -- ADD THE CAPTURE RULE -- [email protected]> BEGIN DBMS_STREAMS_ADM.ADD_TABLE_RULES( table_name => '"LEARNING.EMPLOYEES"', streams_type => 'capture', streams_name => '"STREAMS_CAPTURE"', queue_name => '"STRM_ADMIN"."STREAMS_CAPTURE_Q"', include_dml => true, include_ddl => true, include_tagged_lcr => false, inclusion_rule => true); END; / commit; -- ADD THE PROPAGATION RULE -- [email protected]> BEGIN DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES( table_name => '"LEARNING.EMPLOYEES"', streams_name => '"STREAMS_PROPAGATION"', source_queue_name => '"STRM_ADMIN"."STREAMS_CAPTURE_Q"', destination_queue_name => '"STRM_ADMIN"."STREAMS_APPLY_Q"@STRM2.US.APGTECH.COM', include_dml => true, include_ddl => true, source_database => 'STRM1.US.APGTECH.COM', inclusion_rule => true); END; / COMMIT; -- INSTANTIATE TABLE ACROSS DBLINK -- [email protected]> DECLARE -- Variable to hold instantiation System Change Number iscn NUMBER; BEGIN iscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER(); [email protected]( source_object_name => 'LEARNING.EMPLOYEES', source_database_name => 'STRM1.US.APGTECH.COM', instantiation_scn => iscn); END; / COMMIT;
-- On STRM2
log in as STRM_ADMIN
-- ADD THE QUEUE a good queue name is STREAMS_APPLY_Q
-- [email protected]> BEGIN DBMS_STREAMS_ADM.SET_UP_QUEUE( queue_table => '"STREAMS_APPLY_QT"', queue_name => '"STREAMS_APPLY_Q"', queue_user => '"STRM_ADMIN"'), END; / COMMIT; -- ADD THE APPLY RULE -- [email protected]> BEGIN DBMS_STREAMS_ADM.ADD_TABLE_RULES( table_name => '"LEARNING.EMPLOYEES"', streams_type => 'apply', streams_name => '"STREAMS_APPLY"', queue_name => '"STRM_ADMIN"."STREAMS_APPLY_Q"', include_dml => true, include_ddl => true, include_tagged_lcr => false, inclusion_rule => true); END; / commit;
Start everything up and test.
-- [email protected]> BEGIN DBMS_APPLY_ADM.SET_PARAMETER( apply_name => 'STREAMS_APPLY', parameter => 'disable_on_error', value => 'n'), END; / COMMIT; -- [email protected]> DECLARE v_started number; BEGIN SELECT DECODE(status, 'ENABLED', 1, 0) INTO v_started FROM DBA_APPLY where apply_name = 'STREAMS_APPLY'; if (v_started = 0) then DBMS_APPLY_ADM.START_APPLY(apply_name => '"STREAMS_APPLY"'), end if; END; / COMMIT; -- [email protected]> DECLARE v_started number; BEGIN SELECT DECODE(status, 'ENABLED', 1, 0) INTO v_started FROM DBA_CAPTURE where CAPTURE_NAME = 'STREAMS_CAPTURE'; if (v_started = 0) then DBMS_CAPTURE_ADM.START_CAPTURE(capture_name => '"STREAMS_CAPTURE"'), end if; END; /
Then on STRM1
,
-- [email protected]> ACCEPT fname PROMPT 'Enter Your First Name:' ACCEPT lname PROMPT 'Enter Your Last Name:' Insert into LEARNING.EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, TIME) Values (5, '&fname', '&lname', NULL); dbms_lock.sleep(10); --give it time to replicate
On the first record we have found that the Streams take a while to "warm up". That is why we used dbms_lock
above. Once Streams is up and running it runs and runs and runs.
Then on STRM2
, search for the record.
-- [email protected]> Select * from LEARNING.EMPLOYEES;
If everything is working, now is a good time for a break before moving on to the next section. In the next section, we will set up the reverse STRM2
to STRM1
.
Now the plan for setting up Streams for STRM2
. It is the mirror image of what we have done above, except for the test part.
On STRM2
, log in as STRM_ADMIN
.
-- ADD THE QUEUE, a good queue name is STREAMS_CAPTURE_Q
-- ADD THE CAPTURE RULE
-- ADD THE PROPAGATION RULE
-- INSTANTIATE TABLE ACROSS DBLINK
-- DBLINK TO DESTINATION is STRM1.US.APGTECH.COM
-- SOURCE is STRM2.US.APGTECH.COM
On STRM1
log in as STRM_ADMIN
.
-- ADD THE QUEUE: A good queue name is STREAMS_APPLY_Q
-- ADD THE APPLY RULE
Start everything up and test the Stream on STRM2
.
Then check to see if the record is STREAM'ed to STRM1.
-- On STRM2
log in as STRM_ADMIN
-- ADD THE QUEUE :A good queue name is STREAMS_CAPTURE_Q
-- [email protected]> BEGIN DBMS_STREAMS_ADM.SET_UP_QUEUE( queue_table => '"STREAMS_CAPTURE_QT"', queue_name => '"STREAMS_CAPTURE_Q"', queue_user => '"STRM_ADMIN"'), END; / commit; -- ADD THE CAPTURE RULE -- [email protected]> BEGIN DBMS_STREAMS_ADM.ADD_TABLE_RULES( table_name => '"LEARNING.EMPLOYEES"', streams_type => 'capture', streams_name => '"STREAMS_CAPTURE"', queue_name => '"STRM_ADMIN"."STREAMS_CAPTURE_Q"', include_dml => true, include_ddl => true, include_tagged_lcr => false, inclusion_rule => true); END; / commit; -- ADD THE PROPAGATION RULE -- [email protected]> BEGIN DBMS_STREAMS_ADM.ADD_TABLE_PROPAGATION_RULES( table_name => '"LEARNING.EMPLOYEES"', streams_name => '"STREAMS_PROPAGATION"', source_queue_name => '"STRM_ADMIN"."STREAMS_CAPTURE_Q"', destination_queue_name => '"STRM_ADMIN"."STREAMS_APPLY_Q"@STRM1.US.APGTECH.COM', include_dml => true, include_ddl => true, source_database => 'STRM2.US.APGTECH.COM', inclusion_rule => true); END; / COMMIT;
Because the table was instantiated from STRM1
already, you can skip this step.
-- INSTANTIATE TABLE ACROSS DBLINK -- [email protected]> DECLARE iscn NUMBER; -- Variable to hold instantiation SCN value BEGIN iscn := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER(); [email protected]( source_object_name => 'LEARNING.EMPLOYEES', source_database_name => 'STRM1.US.APGTECH.COM', instantiation_scn => iscn); END; / COMMIT;
-- On STRM1
, log in as STRM_ADMIN
.
-- ADD THE QUEUE, a good queue name is STREAMS_APPLY_Q -- [email protected]> BEGIN DBMS_STREAMS_ADM.SET_UP_QUEUE( queue_table => '"STREAMS_APPLY_QT"', queue_name => '"STREAMS_APPLY_Q"', queue_user => '"STRM_ADMIN"'), END; / COMMIT; -- ADD THE APPLY RULE -- [email protected]> BEGIN DBMS_STREAMS_ADM.ADD_TABLE_RULES( table_name => '"LEARNING.EMPLOYEES"', streams_type => 'apply', streams_name => '"STREAMS_APPLY"', queue_name => '"STRM_ADMIN"."STREAMS_APPLY_Q"', include_dml => true, include_ddl => true, include_tagged_lcr => false, inclusion_rule => true); END; / commit;
-- [email protected]> BEGIN DBMS_APPLY_ADM.SET_PARAMETER( apply_name => 'STREAMS_APPLY', parameter => 'disable_on_error', value => 'n'), END; / COMMIT; -- [email protected]> DECLARE v_started number; BEGIN SELECT DECODE(status, 'ENABLED', 1, 0) INTO v_started FROM DBA_APPLY where apply_name = 'STREAMS_APPLY'; if (v_started = 0) then DBMS_APPLY_ADM.START_APPLY(apply_name => '"STREAMS_APPLY"'), end if; END; / COMMIT; -- [email protected]> DECLARE v_started number; BEGIN SELECT DECODE(status, 'ENABLED', 1, 0) INTO v_started FROM DBA_CAPTURE where CAPTURE_NAME = 'STREAMS_CAPTURE'; if (v_started = 0) then DBMS_CAPTURE_ADM.START_CAPTURE(capture_name => '"STREAMS_CAPTURE"'), end if; END; /
Then on STRM2:
-- [email protected]> ACCEPT fname PROMPT 'Enter Your Mom's First Name:' ACCEPT lname PROMPT 'Enter Your Mom's Last Name:' Insert into LEARNING.EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, TIME) Values (5, '&fname', '&lname', NULL); dbms_lock.sleep(10); --give it time to replicate
On the first record we have found the Streams take a while to "warm up". That is why we used dbms_lock
above. Once Streams is up and running, it runs and runs and runs.
Then on STRM1
, search for the record.
-- [email protected]> Select * from LEARNING.EMPLOYEES;
We now have N-way replication.
But wait, what about conflict resolution?
Good catch; all of this was just to set up N-way replication. In this case, it is a 2-way replication. It will work the majority of the time; that is until there is conflict. Conflict resolution needs to be set up and in this example the supplied/built-in conflict resolution handler MAXIMUM will be used. Now, let us cause some CONFLICT! Then we will be good people and create the conflict resolution and ask for world peace while we are at it!