ORA-00001 unique key violated with MERGE

I arrived at work one morning to find a system generated email telling me that a user session had thrown a unique constraint violation exception. Curious given the code had been running in production for 6 months…

The line number for the PL/SQL package that had thrown the exception showed that it came from a MERGE statement. The fact that it was a MERGE that had thrown an ORA-00001 immediately points to a concurrency issue. At first the developer of the code didn’t understand how a MERGE could throw ORA-00001 until I showed them the following example.

Starting with a simple two column table with 10 rows (running under Oracle 12.1.0.2):

CREATE TABLE merge_test
    (id     NUMBER(6) NOT NULL
    ,val    NUMBER(6) NOT NULL
    ,CONSTRAINT merge_test_pk PRIMARY KEY (id))
/

INSERT INTO merge_test
SELECT ROWNUM
,      ROWNUM
FROM   dual
CONNECT BY ROWNUM <= 10
/

COMMIT
/

EXEC dbms_stats.gather_table_stats ('','merge_test')

we run the following MERGE in two sessions, operating against an existing row in the table:

MERGE INTO merge_test dest
USING (SELECT 5    AS id
       ,      20   AS val
       FROM   dual) src
ON (dest.id = src.id)
WHEN MATCHED THEN UPDATE
SET dest.val = src.val
WHEN NOT MATCHED THEN INSERT
   (id, val)
VALUES
   (src.id, src.val)

The first session immediately reports:

1 row merged.

while the second session, attempting to obtain a lock on the same row, is blocked. Once the first session issues a commit or rollback then the second session also reports 1 row merged.

Now let’s repeat this exercise using an ID value for a row that does not exist in the table:

MERGE INTO merge_test dest
USING (SELECT 20   AS id
       ,      20   AS val
       FROM   dual) src
ON (dest.id = src.id)
WHEN MATCHED THEN UPDATE
SET dest.val = src.val
WHEN NOT MATCHED THEN INSERT
   (id, val)
VALUES
   (src.id, src.val)

Session 1 reports the 1 row merged while session 2, after session 1 commits, reports:

MERGE INTO merge_test dest
*
ERROR at line 1:
ORA-00001: unique constraint (DEVELOPER.MERGE_TEST_PK) violated

The reason for this is all related to Oracle’s multi-versioning read consistency model. At the point in time that session 2 executed the MERGE session 1 had not committed the data so session 2 cannot “see” the row that session 1 is about to insert. As a result the MERGE that session 2 executes also attempts to insert a new row. Oracle then blocks session 2 as it detects a uniqueness conflict with the row that session 1 is inserting. Oracle does not immediately report the exception as it needs to wait until session 1 commits or rollbacks its transaction. When session 1 commits the transaction then session 2 throws the ORA-00001 exception. If session 1 had issued a rollback then session 2 would have been permitted to insert the new row.

The solution to this problem is to simply remove the MERGE and replace it with an INSERT/UPDATE combination:

BEGIN
   INSERT INTO merge_test (id, val)
   VALUES (20, 20);
EXCEPTION
   WHEN DUP_VAL_ON_INDEX THEN
      UPDATE merge_test
      SET    val = 20
      WHERE  id = 20;
END;

With this code session 2 will block on its attempt to perform the INSERT. Once session 1 issues a commit then session 2 intercepts the DUP_VAL_ON_INDEX exception that is thrown, which is then caught and the operation is changed to an UPDATE.

Back to the production problem, a scan of the application log files and database entries did indeed show that there were two near simultaneous request that would have resulted in the MERGE being run for the same key, confirming the diagnosis. Another problem solved and, for the developer of the code, another lesson learned.

Advertisement

5 thoughts on “ORA-00001 unique key violated with MERGE

  1. I have two table with the same columns in different databases. Both table have records.i want to insert the records of table2 in table1 but i want to ignore those records which are already in table 1. As well i want to store all ignored records in a new table. Example:

    create table dest
    (id number primary key,
    col1 varchar2(10));

    create table src
    (id number,
    col1 varchar2(10));

    insert into src values(1,’ABC’);
    insert into src values(2,’GHB’);
    insert into src values(3,’DUP’);
    insert into src values(3,’DUP’);

    commit;

    merge into dest
    using
    (select id,col1 from src) src on(dest.id=src.id)
    when not matched then
    insert values(src.id,src.col1)
    when matched
    then update set dest.col1=src.col1;

    Error report – SQL Error: ORA-00001: unique constraint (SCOTT.SYS_C0010807) violated 00001. 00000 – “unique constraint (%s.%s) violated” *Cause: An UPDATE or INSERT statement attempted to insert a duplicate key. For Trusted Oracle configured in DBMS MAC mode, you may see this message if a duplicate entry exists at a different level. *Action: Either remove the unique restriction or do not insert the key.

    • Hi Sam,

      It sounds what you might need in this situation is an INSERT with the LOG ERRORS clause (http://docs.oracle.com/database/121/SQLRF/statements_9014.htm#BGBEIACB). The first step is to create the error log table, using the package DBMS_ERRLOG (specifying the table you want to the error log table to be based on; i.e. DEST):

      EXEC dbms_errlog.create_error_log ('DEST')
      

      This creates a table named ERR$_DEST. We can then run a simple INSERT with the LOG ERROR clause:

      INSERT INTO dest (id, col1)
      SELECT id
      ,      col1
      FROM   src
      LOG ERRORS INTO err$_dest REJECT LIMIT UNLIMITED;
      
      COMMIT;
      

      The error entries from the INSERT will then be in the error log table:

      SELECT *
      FROM   err$_dest;
      

      This is an excellent subject for a new post. Many thanks!

      Regards

      Mark

    • Yes, handling the exception thrown by the MERGE may very well be a solution. It does depend on the specifics of the scenario though. In the post I used a MERGE of just a single row so that would work well. If multiple rows were being merged then the solution will need to be different as there could be a mix of inserts and updates going on. In that scenario the best solution might be to repeat the MERGE operation.

      Regards

      Mark

  2. Pingback: [Solved] Oracle – Prevent unique constraint on first insert of merge – Ten-tools.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s