Reproducing the error caused by concurrent sessions.
First session executes the Update on Product:
08/12/2015 17:46:54:SQL> -- session 1
08/12/2015 17:47:12:SQL> BEGIN
2 UPDATE product pr
3 SET parent_prd_id =
4 (SELECT b.prd_parent_id
5 FROM product_parent_relation_batch b
6 INNER JOIN product p ON b.prd_id = p.prd_id
7 WHERE b.processed = 'F'
8 AND pr.prd_id = p.prd_id)
9 WHERE prd_id in (SELECT p.prd_id
10 FROM product_parent_relation_batch b
11 INNER JOIN product p ON b.prd_id = p.prd_id
12 WHERE b.processed = 'F');
13 END;
14 /
Procedimento PL/SQL concluído com sucesso.
Before the 2nd update happens a different session inserts new rows:
08/12/2015 17:47:31:SQL> -- session 2
08/12/2015 17:47:31:SQL> INSERT INTO product
2 VALUES (990, 'New', null);
1 linha criada.
08/12/2015 17:47:31:SQL> INSERT INTO product_parent_relation_batch
2 VALUES (990, 789, 'F');
1 linha criada.
08/12/2015 17:47:31:SQL>
08/12/2015 17:47:31:SQL> commit;
Commit concluído.
Then, with those new commited rows, our first transaction Updates the Batch table:
08/12/2015 17:47:50:SQL> --- continues
08/12/2015 17:47:50:SQL> UPDATE product_parent_relation_batch pb
2 SET processed = 'T'
3 WHERE pb.prd_id IN (SELECT b.prd_id
4 FROM product_parent_relation_batch b
5 INNER JOIN product p ON b.prd_id = p.prd_id
6 WHERE b.processed = 'F'
7 AND pb.prd_id = p.prd_id);
3 linhas atualizadas.
08/12/2015 17:47:50:SQL> SELECT *
2 FROM product_parent_relation_batch b
3 INNER JOIN product p
4 ON b.prd_id = p.prd_id
5 WHERE p.prd_id = 990;
PRD_ID PRD_PARENT_ID P PRD_ID PRD PARENT_PRD_ID
---------- ------------- - ---------- --- -------------
990 789 T 990 New
08/12/2015 17:47:50:SQL> COMMIT;
Commit concluído.
Notice 3 rows are updated. The error you mentioned is displayed by checking that "New" row that now has 'T' instead of 'F'.
Now let's try it changing it to Serializable Isolation Level:
08/12/2015 17:51:08:SQL> -- session 1
08/12/2015 17:51:24:SQL> BEGIN
2 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
3 UPDATE product pr
4 SET parent_prd_id =
5 (SELECT b.prd_parent_id
6 FROM product_parent_relation_batch b
7 INNER JOIN product p ON b.prd_id = p.prd_id
8 WHERE b.processed = 'F'
9 AND pr.prd_id = p.prd_id)
10 WHERE prd_id in (SELECT p.prd_id
11 FROM product_parent_relation_batch b
12 INNER JOIN product p ON b.prd_id = p.prd_id
13 WHERE b.processed = 'F');
14 END;
15 /
Procedimento PL/SQL concluído com sucesso.
Then concurrent insert:
08/12/2015 17:50:59:SQL> -- session 2
08/12/2015 17:51:46:SQL> INSERT INTO product
2 VALUES (990, 'New', null);
1 linha criada.
08/12/2015 17:51:46:SQL> INSERT INTO product_parent_relation_batch
2 VALUES (990, 789, 'F');
1 linha criada.
08/12/2015 17:51:46:SQL>
08/12/2015 17:51:46:SQL> commit;
Commit concluído.
And finally the 2nd update:
08/12/2015 17:51:24:SQL> --- continues
08/12/2015 17:52:16:SQL> UPDATE product_parent_relation_batch pb
2 SET processed = 'T'
3 WHERE pb.prd_id IN (SELECT b.prd_id
4 FROM product_parent_relation_batch b
5 INNER JOIN product p ON b.prd_id = p.prd_id
6 WHERE b.processed = 'F'
7 AND pb.prd_id = p.prd_id);
2 linhas atualizadas.
08/12/2015 17:52:16:SQL> SELECT *
2 FROM product_parent_relation_batch b
3 INNER JOIN product p
4 ON b.prd_id = p.prd_id
5 WHERE p.prd_id = 990;
não há linhas selecionadas
08/12/2015 17:52:16:SQL> COMMIT;
Commit concluído.
08/12/2015 17:52:16:SQL> SELECT *
2 FROM product_parent_relation_batch b
3 INNER JOIN product p
4 ON b.prd_id = p.prd_id
5 WHERE p.prd_id = 990;
PRD_ID PRD_PARENT_ID P PRD_ID PRD PARENT_PRD_ID
---------- ------------- - ---------- --- -------------
990 789 F 990 New
The new row is untouched, because the Serializable isolation level makes it a snapshot at the beginning of the transaction.
The correct version would be similar to this:
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE product pr
SET parent_prd_id =
(SELECT b.prd_parent_id
FROM product_parent_relation_batch b
INNER JOIN product p ON b.prd_id = p.prd_id
WHERE b.processed = 'F'
AND pr.prd_id = p.prd_id)
WHERE prd_id in (SELECT p.prd_id
FROM product_parent_relation_batch b
INNER JOIN product p ON b.prd_id = p.prd_id
WHERE b.processed = 'F');
UPDATE product_parent_relation_batch pb
SET processed = 'T'
WHERE pb.prd_id IN (SELECT b.prd_id
FROM product_parent_relation_batch b
INNER JOIN product p ON b.prd_id = p.prd_id
WHERE b.processed = 'F'
AND pb.prd_id = p.prd_id);
COMMIT;
END;