Question

I have a coworker working on an application who's run into a problem. He fires off a stored procedure using SqlCommand.ExecuteNonQuery. This stored procedure, in the same table, updates one row and inserts another. Meanwhile his application goes on and reads from the table. A race condition occurs where the read happens in between the update and the insert.

The data in question is records of access levels. When an access level changes it terminates (updates) the old access level and then instantiates (inserts) the new access level. Not infrequently the read will get in between the update and insert and find only terminated access levels--a bit of a problem.

What's the best solution to my coworker's problem?


I got a hold of the stored procedure he's trying to fix:

BEGIN
SELECT OBJECT_ACCESS_ID, PERSON_AUTH_LEVEL
INTO lAccessID, lExistingAccessLevel
FROM SHPAZ.SH_PAZ_OBJECT_ACCESS
WHERE
    USER_ID = pUserID
    AND (GRGR_ID = pGroupID OR (GRGR_ID IS NULL AND pGroupID IS NULL))
    AND SYSDATE BETWEEN OBJECT_ACCESS_EFF_DATE AND OBJECT_ACCESS_END_DATE
FOR UPDATE;

-- If the new access level is the same as the existing, then do nothing.
IF lExistingAccessLevel = pLevel THEN
    RETURN;
END IF;

-- Terminate the existing record.
UPDATE SHPAZ.SH_PAZ_OBJECT_ACCESS
SET OBJECT_ACCESS_END_DATE = SYSDATE
WHERE OBJECT_ACCESS_ID = lAccessID;

-- Create the new record.
SELECT CASE WHEN pGroupID IS NULL THEN 'Broker' ELSE 'Employer' END
INTO lSource
FROM DUAL;

INSERT INTO SHPAZ.SH_PAZ_OBJECT_ACCESS (USER_ID, GRGR_ID, SOURCE, PERSON_AUTH_LEVEL, OBJECT_ACCESS_EFF_DATE, OBJECT_ACCESS_END_DATE) 
VALUES (pUserID, pGroupID, lSource, pLevel, SYSDATE, TO_DATE('12/31/2199', 'MM/DD/YYYY'));

COMMIT;
EXCEPTION
-- If there is no record, then just create a new one.
WHEN NO_DATA_FOUND THEN
    SELECT CASE WHEN pGroupID IS NULL THEN 'Broker' ELSE 'Employer' END
    INTO lSource
    FROM DUAL;

    INSERT INTO SHPAZ.SH_PAZ_OBJECT_ACCESS (USER_ID, GRGR_ID, SOURCE, PERSON_AUTH_LEVEL, OBJECT_ACCESS_EFF_DATE, OBJECT_ACCESS_END_DATE) 
    VALUES (pUserID, pGroupID, lSource, pLevel, SYSDATE, TO_DATE('12/31/2199', 'MM/DD/YYYY'));
END SHSP_SET_USER_ACCESS; 

No correct solution

OTHER TIPS

The solution is to remove the commit from inside your procedure, and have it done after procedure returns. Let's say you create your procedure with name my_procedure:

SQL> exec my_procedure(my_in_arg, my_out_arg);
SQL> commit;

There should be no race at all when atomic functional operations are wrapped inside a transaction.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top