Securing Oracle distributed transactions against network failures - oracle

I am synchronizing a table in a local database with data from a table on a database on the opposite side of the earth using distributed transactions.
The networks are connected through vpn over the internet.
Most of the time it works fine, but when the connection is disrupted during an active transaction, a lock is preventing the job from running again.
I cannot kill the locking session. Trying to do so just returns "ORA-00031: Session marked for kill" and it is not actually killed before i cycle the local database.
The sync job is basically
CURSOR TRANS_CURSOR IS
SELECT COL_A, COL_B, COL_C
FROM REMOTE_MASTERTABLE#MY_LINK
WHERE UPDATED IS NULL;
BEGIN
FOR TRANS IN TRANS_CURSOR LOOP
INSERT INTO LOCAL_MASTERTABLE
(COL_A, COL_B, COL_C)
VALUES
(TRANS.COL_A, TRANS.COL_B, TRANS.COL_C);
INSERT INTO LOCAL_DETAILSTABLE (COL_A, COL_D, COL_E)
SELECT COL_A, COL_D, COL_E
FROM REMOTE_DETAILSTABLE#MY_LINK
WHERE COL_A = TRANS.COL_A;
UPDATE REMOTE_MASTERTABLE#MY_LINK SET UPDATED = 1 WHERE COL_A = TRANS.COL_A;
END LOOP;
END;
Any ideas to make this sync operation more tolerant to network dropouts would be greatly appreciated.
I use Oracle Standard Edition One, so no Enterprise features are available.
TIA
Søren

First off, do you really need to roll your own replication solution? Oracle provides technologies like Streams that are designed to allow you to replicate data changes from one system to another reliably without depending on the database link being always available. That also minimizes the amount of code you have to write and the amount of maintenance you have to perform.
Assuming that your application does need to be configured this way, Oracle will have to use the two-phase commit protocol to ensure that the distributed transaction happens atomically. It sounds like transactions are being left in an in-doubt state. You should be able to see information about in-doubt transactions in the DBA_2PC_PENDING view. You should then be able to manually handle that in-doubt transaction.

You may want to use bulk processing instead of looping. Bulk DML can often give huge performance gains, and if there is a large amount of network lag then the difference may be dramatic if Oracle is retrieving one row at a time. Decreasing the run time won't fix the error, but it should help avoid it. (Although Oracle may already be doing this optimization behind the scenes.)
EDIT
Bulk processing might help, but the best solution would probably be to use only SQL statements. I did some testing and the below version ran about 20 times faster than the original. (Although it's difficult to know how closely my sample data and self-referencing database link model your real data.)
BEGIN
INSERT INTO LOCAL_MASTERTABLE
(COL_A, COL_B, COL_C)
SELECT COL_A, COL_B, COL_C
FROM REMOTE_MASTERTABLE#MY_LINK
WHERE UPDATED IS NULL;
INSERT INTO LOCAL_DETAILSTABLE (COL_A, COL_D, COL_E)
SELECT REMOTE_DETAILSTABLE.COL_A, REMOTE_DETAILSTABLE.COL_D, REMOTE_DETAILSTABLE.COL_E
FROM REMOTE_DETAILSTABLE#MY_LINK
INNER JOIN (SELECT COL_A FROM REMOTE_MASTERTABLE#MY_LINK WHERE UPDATED IS NULL) TRANS
ON REMOTE_DETAILSTABLE.COL_A = TRANS.COL_A;
UPDATE REMOTE_MASTERTABLE#MY_LINK SET UPDATED = 1 WHERE UPDATED IS NULL;
END;
/

Related

Extract sql_id from hash_value

I have sql hash value of a query.
I don't have the sqltext, plan hash or sql_id - only the hash value of the sql.
When I'm querying the v$sql view there are no result (I guess the sql is not in the memory already):
select * from v$sql where hash_value = 'hv_Example';
Is there a way to achieve the sql_id from the hash_value?
I didn't found a view that holds the hash value and the sql_id togather...
V$SQL contains both HASH_VALUE and SQL_ID, but not forever.
According to Tanel Poder's blog, the HASH_VALUE can be derived from the SQL_ID, but the inverse operation is not possible. But converting HASH_VALUE to SQL_ID wouldn't solve your problem anyway.
First of all, I'm not sure why you would have access to the HASH_VALUE but not the SQL_ID. The SQL_ID is much more commonly used for performance tuning. Whatever program or source is providing the HASH_VALUE should be modified to return the SQL_ID instead.
But the real problem here, whichever value you have, is that data is not permanently stored in V$SQL. As statements get old they are aged out of the shared pool, and disappear from V$SQL. Also, if you're using a clustered system, make sure you're using GV$SQL.
If you are using Enterprise Edition, and have licensed the Oracle Diagnostic Pack, you may be able to find historical SQL_ID values with one of these two statements:
select sql_id
from gv$active_session_history
where sql_full_plan_hash_value = 'XYZ';
select sql_id
from dba_hist_active_sess_history
where sql_full_plan_hash_value = 'XYZ';
Data in GV$ACTIVE_SESSION_HISTORY typically lasts for about a day. Data in DBA_HIST_* stays for 8 days by default, but is configurable. You can check the AWR retention with this query: select retention from dba_hist_wr_control;
If the HASH_VALUE is less old than the retention period, but still not in AWR, then you can probably ignore it. ASH and AWR use sampling, which means they do not capture everything that happens in a database. ASH takes a sample every second and AWR takes a sample every 10 seconds.
A lot of people struggle with this concept but it's helpful to come to terms with the idea of sampling. Systems that capture all of the information, like tracing everything, are overwhelming and use up too much space and processing power. If we're looking at performance problems then we do not care about something that doesn't happen for more than 10 seconds out of 8 days.

Oracle Temporary Table to convert a Long Raw to Blob

Questions have been asked in the past that seems to handle pieces of my full question, but I'm not finding a totally good answer.
Here is the situation:
I'm importing data from an old, but operational and production, Oracle server.
One of the columns is created as LONG RAW.
I will not be able to convert the table to a BLOB.
I would like to use a global temporary table to pull out data each time I call to the server.
This feels like a good answer, from here: How to create a temporary table in Oracle
CREATE GLOBAL TEMPORARY TABLE newtable
ON COMMIT PRESERVE ROWS
AS SELECT
MYID, TO_LOB("LONGRAWDATA") "BLOBDATA"
FROM oldtable WHERE .....;
I do not want the table hanging around, and I'd only do a chunk of rows at a time, to pull out the old table in pieces, each time killing the table. Is it acceptable behavior to do the CREATE, then do the SELECT, then DROP?
Thanks..
--- EDIT ---
Just as a follow up, I decided to take an even different approach to this.
Branching the strong-oracle package, I was able to do what I originally hoped to do, which was to pull the data directly from the table without doing a conversion.
Here is the issue I've posted. If I am allowed to publish my code to a branch, I'll post a follow up here for completeness.
Oracle ODBC Driver Release 11.2.0.1.0 says that Prefetch for LONG RAW data types is supported, which is true.
One caveat is that LONG RAW can technically be up to 2GB in size. I had to set a hard max size of 10MB in the code, which is adequate for my use, so far at least. This probably could be a variable sent in to the connection.
This fix is a bit off original topic now however, but it might be useful to someone else.
With Oracle GTTs, it is not be necessary to drop and create each time, and you don't need to worry about data "hanging around." In fact, it's inadvisable to drop and re-create. The structure itself persists, but the data in it does not. The data only persists within each session. You can test this by opening up two separate clients, loading data with one, and you will notice it's not there in the second client.
In effect, each time you open a session, it's like you are reading a completely different table, which was just truncated.
If you want to empty the table within your stored procedure, you can always truncate it. Within a stored proc, you will need to execute immediate if you do this.
This is really handy, but it also can make debugging a bear if you are implementing GTTs through code.
Out of curiosity, why a chunk at a time and not the entire dataset? What kind of data volumes are you talking about?
-- EDIT --
Per our comments conversation, this is very raw and untested, but I hope it will give you an idea what I mean:
CREATE OR REPLACE PROCEDURE LOAD_DATA()
AS
TOTAL_ROWS number;
TABLE_ROWS number := 1;
ROWS_AT_A_TIME number := 100;
BEGIN
select count (*)
into TOTAL_ROWS
from oldtable;
WHILE TABLE_ROWS <= TOTAL_ROWS
LOOP
execute immediate 'truncate table MY_TEMP_TABLE';
insert into MY_TEMP_TABLE
SELECT
MYID, TO_LOB(LONGRAWDATA) as BLOBDATA
FROM oldtable
WHERE ROWNUM between TABLE_ROWS and TABLE_ROWS + ROWS_AT_A_TIME;
insert into MY_REMOTE_TABLE#MY_REMOTE_SERVER
select * from MY_TEMP_TABLE;
commit;
TABLE_ROWS := TABLE_ROWS + ROWS_AT_A_TIME;
END LOOP;
commit;
end LOAD_DATA;

I am inserting millions of records in a table and in other session I want to know the status of the same

I am inserting millions of records in a table
In other session I want to know the status of the same that is how many records have been processed,
No commit is issued in first session
Query v$session_longops. Not 100% but gives a good sense of the session state.
If you have oracle 11g i propose usage of DBMS_PARALLEL_EXECUTE package which allows you to create task chunks and monitoring the process flow (in your case number processed rows). I found tutorial resolving similar case to your situation: http://www.oraclefrontovik.com/2013/05/getting-started-with-dbms_parallel_execute/
Create a procedure with pragma AUTONOMOUS_TRANSACTION which update your counters in another table. Other sessions will see the counters but your main session still not commit.
You may update the counter when you reach 1000 records or how frequently you need it. Don't forget to update the counters when you rollback in main session.
example of procedure
If you're doing inserts in a loop try this construct:
FOR cur IN (SELECT rownum rn, something ... FROM something_else ...)
LOOP
INSERT INTO somewhere ...
dbms_application_info.set_module('Doing inserts', cur.rn || ' rows done');
END LOOP;
Then check module and action columns in v$session view.
First, find SQL_ID of Your session in GV$SQL_MONITOR. Then use it to find Your query's progress and details:
SELECT SQL_ID, status, PROCESS_NAME, FIRST_REFRESH_TIME, LAST_CHANGE_TIME
,PLAN_LINE_ID,PLAN_DEPTH,LPAD(' ',PLAN_DEPTH)||PLAN_OPERATION AS OPER
,PLAN_OPTIONS, PLAN_OBJECT_NAME, PLAN_OBJECT_TYPE
,OUTPUT_ROWS
FROM GV$SQL_PLAN_MONITOR
WHERE SQL_ID IN ( '0u063q75nbjt7' ) -- Your SQL_ID
order by sql_id,process_name,plan_line_id;
Once You found SQL_ID of Your query You can monitor progress in (G)V$SQL_PLAN_MONITOR and processed rows in OUTPUT_ROWS column.
The status can sometimes be derived from the table's segment size.
This is usually not the best approach. But often times it is too late to add logging, and tools like v$session_longops and SQL Monitoring frequently fail to provide accurate estimates, or any estimates at all.
Estimating by the segment size can be very tricky. You must be able to estimate the final size, perhaps based on another environment. Progress may not be linear; some operations may take an hour, and then writing to the table takes the last hour. Direct-path writes will not write to the table's segment initially, instead there will be a temporary segment with an unusual name.
Start with a query like this:
select sum(bytes)/1024/1024 mb
from dba_segments
where segment_name = '[table name]';

How to insert while avoiding unique constraints with oracle

We have a process that aggregates some data and inserts the results into another table that we use for efficient querying. The problem we're facing is that we now have multiple aggregators running at roughly the same time.
We use the original records id as the primary key in this new table - a unique constraint. However, if two aggregation processes are running at the same time, one of them will error with a unique constraint violation.
Is there a way to specify some kind of locking mechanism which will make the second writer wait until the first is finished? Alternatively, is there a way to tell oracle to ignore that specific row and continue with the rest?
Unfortunately it's not practical to reduce the aggregation to a single process, as the following procedures rely on an up to date version of the data being available and those procedures do need to scale out.
Edit:
The following is my [redacted] query:
INSERT INTO
agg_table
SELECT
h.id, h.col, h.col2
FROM history h
JOIN call c
ON c.callid = h.callid
WHERE
h.id > (SELECT coalesce(max(id),0) FROM agg_table)
It is possible run an INSERT statement with an error logging clause. The example from the Oracle docs is as follows:
INSERT INTO dw_empl
SELECT employee_id, first_name, last_name, hire_date, salary, department_id
FROM employees
WHERE hire_date > sysdate - 7
LOG ERRORS INTO err_empl ('daily_load') REJECT LIMIT 25
Alternatively, you could try using a [MERGE][2] statement. You would be merging into the summary table with a select from the detail table. If a match is not found, you INSERT and if it is found you would UPDATE. I believe this solution will handle your concurrency issues, but you would need to test it.
have a look at FOR UPDATE clause. If you correctly write the SELECT statement with FOR UPDATE clause within a transaction before your update/insert statements you will be able to "lock" the required records
Serialising the inserts is probably the best way, as there's no method that will get you round the problem of the multiple inserts being unable to see what each one is doing.
DBMS_Lock is probably the appropriate serialisation mechanism.

How to find locked rows in Oracle

We have an Oracle database, and the customer account table has about a million rows. Over the years, we've built four different UIs (two in Oracle Forms, two in .Net), all of which remain in use. We have a number of background tasks (both persistent and scheduled) as well.
Something is occasionally holding a long lock (say, more than 30 seconds) on a row in the account table, which causes one of the persistent background tasks to fail. The background task in question restarts itself once the update times out. We find out about it a few minutes after it happens, but by then the lock has been released.
We have reason to believe that it might be a misbehaving UI, but haven't been able to find a "smoking gun".
I've found some queries that list blocks, but that's for when you've got two jobs contending for a row. I want to know which rows have locks when there's not necessarily a second job trying to get a lock.
We're on 11g, but have been experiencing the problem since 8i.
Oracle's locking concept is quite different from that of the other systems.
When a row in Oracle gets locked, the record itself is updated with the new value (if any) and, in addition, a lock (which is essentially a pointer to transaction lock that resides in the rollback segment) is placed right into the record.
This means that locking a record in Oracle means updating the record's metadata and issuing a logical page write. For instance, you cannot do SELECT FOR UPDATE on a read only tablespace.
More than that, the records themselves are not updated after commit: instead, the rollback segment is updated.
This means that each record holds some information about the transaction that last updated it, even if the transaction itself has long since died. To find out if the transaction is alive or not (and, hence, if the record is alive or not), it is required to visit the rollback segment.
Oracle does not have a traditional lock manager, and this means that obtaining a list of all locks requires scanning all records in all objects. This would take too long.
You can obtain some special locks, like locked metadata objects (using v$locked_object), lock waits (using v$session) etc, but not the list of all locks on all objects in the database.
you can find the locked tables in Oracle by querying with following query
select
c.owner,
c.object_name,
c.object_type,
b.sid,
b.serial#,
b.status,
b.osuser,
b.machine
from
v$locked_object a ,
v$session b,
dba_objects c
where
b.sid = a.session_id
and
a.object_id = c.object_id;
Rather than locks, I suggest you look at long-running transactions, using v$transaction. From there you can join to v$session, which should give you an idea about the UI (try the program and machine columns) as well as the user.
Look at the dba_blockers, dba_waiters and dba_locks for locking. The names should be self explanatory.
You could create a job that runs, say, once a minute and logged the values in the dba_blockers and the current active sql_id for that session. (via v$session and v$sqlstats).
You may also want to look in v$sql_monitor. This will be default log all SQL that takes longer than 5 seconds. It is also visible on the "SQL Monitoring" page in Enterprise Manager.
The below PL/SQL block finds all locked rows in a table. The other answers only find the blocking session, finding the actual locked rows requires reading and testing each row.
(However, you probably do not need to run this code. If you're having a locking problem, it's usually easier to find the culprit using GV$SESSION.BLOCKING_SESSION and other related data dictionary views. Please try another approach before you run this abysmally slow code.)
First, let's create a sample table and some data. Run this in session #1.
--Sample schema.
create table test_locking(a number);
insert into test_locking values(1);
insert into test_locking values(2);
commit;
update test_locking set a = a+1 where a = 1;
In session #2, create a table to hold the locked ROWIDs.
--Create table to hold locked ROWIDs.
create table locked_rowids(the_rowid rowid);
--Remove old rows if table is already created:
--delete from locked_rowids;
--commit;
In session #2, run this PL/SQL block to read the entire table, probe each row, and store the locked ROWIDs. Be warned, this may be ridiculously slow. In your real version of this query, change both references to TEST_LOCKING to your own table.
--Save all locked ROWIDs from a table.
--WARNING: This PL/SQL block will be slow and will temporarily lock rows.
--You probably don't need this information - it's usually good enough to know
--what other sessions are locking a statement, which you can find in
--GV$SESSION.BLOCKING_SESSION.
declare
v_resource_busy exception;
pragma exception_init(v_resource_busy, -00054);
v_throwaway number;
type rowid_nt is table of rowid;
v_rowids rowid_nt := rowid_nt();
begin
--Loop through all the rows in the table.
for all_rows in
(
select rowid
from test_locking
) loop
--Try to look each row.
begin
select 1
into v_throwaway
from test_locking
where rowid = all_rows.rowid
for update nowait;
--If it doesn't lock, then record the ROWID.
exception when v_resource_busy then
v_rowids.extend;
v_rowids(v_rowids.count) := all_rows.rowid;
end;
rollback;
end loop;
--Display count:
dbms_output.put_line('Rows locked: '||v_rowids.count);
--Save all the ROWIDs.
--(Row-by-row because ROWID type is weird and doesn't work in types.)
for i in 1 .. v_rowids.count loop
insert into locked_rowids values(v_rowids(i));
end loop;
commit;
end;
/
Finally, we can view the locked rows by joining to the LOCKED_ROWIDS table.
--Display locked rows.
select *
from test_locking
where rowid in (select the_rowid from locked_rowids);
A
-
1
Given some table, you can find which rows are not locked with SELECT FOR UPDATESKIP LOCKED.
For example, this query will lock (and return) every unlocked row:
SELECT * FROM mytable FOR UPDATE SKIP LOCKED
References
Ask TOM "How to get ROWID for locked rows in oracle".

Resources