FOR UPDATE simulation in SQL Server and CUBRID

Pessimistic locking is useful every now and then when you explicitly want to lock a given set of rows for a subsequent UPDATE, or for database-based inter-process synchronisation and locking. Normally, you would use the SQL Standard FOR UPDATE clause, which is supported in most databases.

A SQL standard example

-- This statement will produce a row-lock on three rows
-- ... or fail / timeout if the row-lock cannot be acquired
SELECT *
  FROM author
 WHERE id IN (3, 4, 5)
   FOR UPDATE

-- This statement will produce cell-locks on two cells in three rows
-- ... or fail / timeout if the cell-lock cannot be acquired
SELECT *
  FROM author
 WHERE id in (3, 4, 5)
   FOR UPDATE OF first_name, last_name

An Oracle syntax extension example

Oracle ships with some useful extensions to the FOR UPDATE clause:

-- This statement will produce a row-lock on three rows
-- skipping all locked records. This is very nice for queue tables where
-- you never want to fail on locks, but just skip to the next record
SELECT *
  FROM author
 WHERE id IN (3, 4, 5)
   FOR UPDATE SKIP LOCKED

Simulation of the FOR UPDATE clause

SQL Server supports FOR UPDATE only in cursors (as defined in the SQL standard). It also ships with proprietary SQL syntax extensions, such as WITH (updlock), that has some very peculiar “features”, such as locking the whole page of a record instead of just one record. The CUBRID database on the other hand doesn’t support pessimistic locking at all in SQL.

But this can be simulated with JDBC, as JDBC allows for specifying the flags TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE for any statement, and then using ResultSet.updateXXX() methods to produce a cell-lock / row-lock. Here’s a simplified example in JDBC:

PreparedStatement stmt = connection.prepareStatement(
  "SELECT * FROM author WHERE id IN (3, 4, 5)",
  ResultSet.TYPE_SCROLL_SENSITIVE, 
  ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery();

while (rs.next()) {

  // UPDATE the primary key for row-locks, or any other
  // columns for cell-locks
  rs.updateObject(1, rs.getObject(1));
  rs.updateRow();

  // Do more stuff with this record
}

Drawbacks of this approach

The main drawback of this approach is the fact that the database has to maintain a scrollable cursor, whose records are locked one by one. This can cause a major risk of deadlocks or race conditions if the JDBC driver can recover from the unsuccessful locking, if two Java threads execute the following statements:

-- thread 1
SELECT * FROM author ORDER BY id ASC;

-- thread 2
SELECT * FROM author ORDER BY id DESC;

So use this technique with care, possibly only ever locking single rows!

An example using jOOQ to abstract this simulation

The following example shows how jOOQ integration tests test pessimistic locking for all SQL dialects, including SQL Server and CUBRID:

// Two factories with different connections
Factory create1 = // ...
Factory create2 = // ...

// A synchronized list to check for correct execution order
final Vector<String> execOrder = new Vector<String>();

try {
    // This is the test case:
    // 0.0s: Both threads start
    // 0.0s: t1 sleeps for 2s
    // 0.0s: t2 locks the AUTHOR table
    // 0.1s: t2 sleeps for 4s
    // 2.0s: t1 blocks on the AUTHOR table (potentially failing here)
    // 4.0s: t2 commits and unlocks AUTHOR
    // 4.1s: t1 (if blocked) now locks the AUTHOR table

    // For simplicity, no ReentrantLocks and signals are used for
    // synchronisation, but simple Thread.sleep() calls.

    final Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            Thread.sleep(2000);
            execOrder.add("t1-block");
            try {
                create1
                    .select(AUTHOR.ID)
                    .from(AUTHOR)
                    .forUpdate()
                    .fetch();
            }

            // Some databases fail on locking, others lock for a while
            catch (Exception ignore) {
            }
            finally {
                execOrder.add("t1-fail-or-t2-commit");
            }
        }
    });

    final Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            execOrder.add("t2-exec");
            Result<?> result2 = create2
                .selectFrom(AUTHOR)
                .forUpdate()
                .fetch();
            assertEquals(2, result2.size());

            execOrder.add("t2-signal");

            try {
                Thread.sleep(4000);
            }
            catch (Exception ignore) {
            }
 
            execOrder.add("t1-fail-or-t2-commit");

            try {
                create2.getConnection().commit();
            }
            catch (Exception e) {}
        }
    });

    t1.start();
    t2.start();

    t1.join();
    t2.join();

    assertEquals(asList(
      "t2-exec", 
      "t2-signal", 
      "t1-block", 
      "t1-fail-or-t2-commit", 
      "t1-fail-or-t2-commit"), execOrder);
}

The above test shows how the FOR UPDATE clause is implemented or simulated for most databases in jOOQ. Some databases use a timeout on the lock acquisition before failing, while others fail immediately (Oracle allows for specifying that in the FOR UPDATE WAIT / NOWAIT clause)

One thought on “FOR UPDATE simulation in SQL Server and CUBRID

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 )

Google+ photo

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

Connecting to %s