The Artima Developer Community
Sponsored Link

Design Forum
Database transactions and exceptions don't mix! (need design help)

2 replies on 1 page. Most recent reply: Oct 12, 2010 3:10 AM by Jane Xie

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 2 replies on 1 page
Adam B

Posts: 1
Nickname: cruxic
Registered: Aug, 2009

Database transactions and exceptions don't mix! (need design help) Posted: Aug 7, 2009 2:39 PM
Reply to this message Reply
Advertisement
Lately I've been trying to improve how our web application employs transactions (we don't use them enough). While doing this I became aware of this rather general problem. It seems as though database transactions cannot be mixed with exceptions unless great vigilance is practiced. Consider this pseudo code:

function doOperationX()
{
connection = get_connection()
connection.start_transaction()
try
{
updateA(connection)

try
{
updateB(connection)
}
catch (C_DoesNotExist)
{
//ignore (non fatal)
}

connection.commit()
}
finally
{
connection.end_transaction() //rolls back uncommitted
connection.close()
}
}

function updateA(connection)
{
connection.execute("UPDATE a SET ...")
}

function updateB(connection)
{
connection.execute("UPDATE b SET ...")
updateC(connection)
}

function updateC(connection)
{
result = connection.execute("SELECT c.id FROM c INNER JOIN b ON ...")
if (result.next())
connection.execute("UPDATE c SET ...")
else
throw C_DoesNotExist()
}


Don't study the pseudo SQL too closely, the problem isn't there. Read the code like this:

1) operationX requires updating of A and, optionally, B.
2) Internally, updating B always requires that C also be updated. All or nothing.
3) Sometimes, C cannot be updated because the database is not in the proper state at this time. (Not an invalid state, mind you)
4) operationX knows that C might not be updatable, so it catches the exception and ignores it.

The problem is:

updateB() leaves the database in an inconsistent state if an exception is thrown out of updateC(). The inconsistency is that table B has been updated but D has not been updated accordingly.

Yet, it's only a problem because operationX caught the exception. Had it let the exception continue up, the finally block would have rolled back the entire transaction quite nicely.

How can I avoid this problem? The only proper solution that I can see is a prolific use of transaction checkpoints (aka savepoints) so that a partial rollback can occur if needed:

function updateB(connection)
{
success = false
savepoint = connection.setSavepoint()
try
{
connection.execute("UPDATE b SET ...")
updateD(connection)
success = true
}
finally
{
if (not success)
connection.rollback(savepoint)
}
}


But this hurts performance considerably and requires programmer vigilance and lots of extra code! The programmer must remember that:

If your function does more than one update it MUST use a savepoint!

That's tough pill to swallow (for me). I try to avoid programming models that require programmer vigilance. All it takes is one groggy Monday...

What do you think? Am I missing a better solution?


Humberto Madeira

Posts: 5
Nickname: kunakida
Registered: Apr, 2008

Re: Database transactions and exceptions don't mix! (need design help) Posted: Jun 13, 2010 8:49 AM
Reply to this message Reply
Instead of just ignoring the C_DoesNotExist exception after it is caught, why don't you just rethrow it and let the outer try block also deal with it.

Or am I missing some reason why this shouldn't be done?

Jane Xie

Posts: 1
Nickname: jaybridge
Registered: Oct, 2010

Re: Database transactions and exceptions don't mix! (need design help) Posted: Oct 12, 2010 3:10 AM
Reply to this message Reply
From the pseudo code, I gather that if updateB() throws an exception, there is no need to rollback operation done in updateA(). If that is the case, you may consider committing after updateA() and rollbacking operation of updateB() in the catch block; that is easier than doing savepoint.

function doOperationX()
{
connection = get_connection()
connection.start_transaction()

try
{
updateA(connection)
connection.commit()

try
{
updateB(connection)
}
catch (C_DoesNotExist)
{
//ignore (non fatal) -- original
connection.rollback() //
}

connection.commit()
}
finally
{
connection.end_transaction() //rolls back uncommitted
connection.close()
}
}

Flat View: This topic has 2 replies on 1 page
Topic: Design for performance Previous Topic   Next Topic Topic: Development of 64-bit C/C++ applications. Lessons.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use