Search code examples
db2lockingdatabase-cursordb2-zosselect-for-update

DB2 for z/OS: CURSOR FOR UPDATE locking behavior


I have a question concerning the FOR UPDATE Clause in CURSORs for IBM DB2 for z/OS. Assume Isolation Level Cursor Stability (standard parameter in BIND command). DB2 Version is 11.

My first question is: can a CURSOR that is coded with the FOR UPDATE clause prevent concurrent transactions form reading the row on which the CURSOR is currently positioned on? My second question is: does the UPDATE ... WHERE CURRENT OF ... statement detect when the updated row has been changed after the CURSOR has been opened and before it has been fetched from the CURSORs resultset?

I have read some contradictory statements on the web regarding these questions. As of my (current) understanding, the FETCH operation only aquires an update lock on the fetched row, so concurrent transactions can at least read the same row. The U-Lock is only promoted to an X-Lock in case the UPDATE WHERE CURRENT OF CURSOR is actually done (dependent on application logic). But this confuses me, because it then would not prevent a lost update phenomenon (when the concurrent process is allowed to read the value before the update in the first process is done it continues its processing with the old value and overwrites the update of the first process which has updated via CURRENT OF CURSOR).


Solution

  • Can a cursor that is coded with the FOR UPDATE clause prevent concurrent transactions from reading the row on which the cursor is currently positioned?

    No - with isolation level CS, Db2 will hold a U lock on the current row which is compatible with the S locks potentially required (see later comments about the CURRENTDATA bind parameter and it's impact on avoidance of the S lock for readers).

    Does the UPDATE ... WHERE CURRENT OF statement detect when the updated row has been changed after the cursor has been opened and before it has been fetched from the CURSORs result set?

    No - with isolation level CS, Db2 will not acquire a lock until the row is read. If you require the data to remain unchanged after OPEN CURSOR you need a different isolation level.

    But this confuses me, because it then would not prevent a lost update phenomenon (when the concurrent process is allowed to read the value before the update in the first process is done it continues its processing with the old value and overwrites the update of the first process which has updated via CURRENT OF CURSOR).

    Assuming both transactions are using FOR UPDATE and UPDATE ... WHERE CURRENT OF this scenario cannot happen. Each read would attempt to acquire a U lock. Since U locks are incompatible with each other the second read would wait on the first U lock to be released. (https://www.ibm.com/docs/en/db2-for-zos/12?topic=locks-lock-modes-compatibility)


    For the more complex case where one (or both) of the transactions are not using FOR UPDATE and UPDATE ... WHERE CURRENT OF there are opportunities for the lost update phenomenon to occur.

    Long ago, Db2 introduced bind parameter CURRENTDATA to help control this behavior.

    • CURRENTDATA(NO) (default as of Db2 10) - Attempt lock avoidance where possible but with an increased risk of obtaining non-current data
    • CURRENTDATA(YES) - Acquire S locks to reduce the risk of obtaining non-current data. It's important to note that CURRENTDATA(YES) does not completely eliminate the risk of non-current data.

    Db2 manual - Choosing CURRENTDATA Option

    Gareth has some great articles on this with much more detail - Db2 for z/OS Locking for Application Developers Part 8

    To completely guard against the risk of losing an update, a good approach is to add predicates to ensure the update only occurs against the expected data. Gareth provides three options for this in Part 9 of his blog on locking. The general idea is to have something like an update timestamp that is always updated when any part of the row is updated. Then include a predicate in the WHERE clause of the UPDATE statement to ensure that the update will only occur if the update timestamp is the same as when the row was originally read. The ROW CHANGE TIMESTAMP feature in Db2 9 makes this approach easier.