views:

237

answers:

4

We have a SQL Server database table that consists of user id, some numeric value, e.g. balance, and a version column.

We have multiple threads updating this table's value column in parallel, each in its own transaction and session (we're using a session-per-thread model). Since we want all logical transaction to occur, each thread does the following:

  1. load the current row (mapped to a type).
  2. make the change to the value, based on old value. (e.g. add 50).
  3. session.update(obj)
  4. session.flush() (since we're optimistic, we want to make sure we had the correct version value prior to the update)
  5. if step 4 (flush) threw StaleStateException, refresh the object (with lockmode.read) and goto step 1

we only do this a certain number of times per logical transaction, if we can't commit it after X attempts, we reject the logical transaction.

each such thread commits periodically, e.g. after 100 successful logical transactions, to keep commit-induced I/O to manageable levels. meaning - we have a single database transaction (per transaction) with multiple flushes, at least once per logical change.

what's the problem here, you ask? well, on commits we see changes to failed logical objects. specifically, if the value was 50 when we went through step 1 (for the first time), and we tried to update it to 100 (but we failed since e.g. another thread changed it to 70), then the value of 50 is committed for this row. obviously this is incorrect.

What are we missing here?

A: 

I'm not a nhibernate guru, but answer seems simple.

When nhibernate loads an object, it expects it not to change in db as long as it's in nhibernate session cache.

As you mentioned - you got multi thread app.

This is what happens=>

  • 1st thread loads an entity
  • 2nd thread loads an entity
  • 1st thread changes entity
  • 2nd thread changes entity and => finds out that loaded entity has changed by something else and being afraid that it has screwed up changes 1st thread made - throws an exception to let programmer be aware about that.

You are missing locking mechanism. Can't tell much about how to apply that properly and elegantly. Maybe Transaction would help.

We had similar problems when we used nhibernate and raw ado.net concurrently (luckily - just for querying - at least for production code). All we had to do - force updating db on insert/update so we could actually query something through full-text search for some specific entities.

Had StaleStateException in integration tests when we used raw ado.net to reset db. NHibernate session was alive through bunch of tests, but every test tried to cleanup db without awareness of NHibernate.

Arnis L.
actually, we're using transactions across many such atomic, optimistic updates. I made a minor change to the question to clear that up.But - I'm not sure I understand your answer. 2nd thread can't just "find out" that another instance of the loaded. they are using different sessions hence objects aren't shared. Also added this to question.
Shachar
It finds out that *STATE* (some fields modified) has changed not that another session has loaded it. Actually - there wouldn't be problem if the same session was used, 2nd thread would be aware about changes (still - i'm not sure that usage of 1 session is appropriate for your case).
Arnis L.
@Arnis, you're missing an important distinction here. We do not need application-level locks (on purpose) since each thread has its own copy of the data - hence no detection (or application) of state changes by another thread, ever.
Shachar
I suppose that conflicts with nature of NHibernate. Should be changed with some NHibernate configuration.
Arnis L.
+2  A: 

Well, I do not have a ton of experience here, but one thing I remember reading in the documentation is that if an exception occurs, you are supposed to immediately rollback the transaction and dispose of the session. Perhaps your issue is related to the session being in an inconsistent state?

Also, calling update in your code here is not necessary. Since you loaded the object in that session, it is already being tracked by nhibernate.

Chris Shaffer
@Chris, can you provide a reference to the documentation where it says so? I can't find it. It sounds weird and broken, really. (why would an exception invalidate the entire session?)
Shachar
http://nhforge.org/doc/nh/en/index.html#manipulatingdata-exceptions
Chris Shaffer
+1  A: 

If you want to make your changes anyway, why do you bother with row versioning? It sounds like you should get the same result if you simply always update the data and let the last transaction win.

As to why the update becomes permanent, it depends on what the SQL statements for the version check/update look like and on your transaction control, which you left out of the code example. If you turn on the Hibernate SQL logging it will probably become obvious how this is happening.

FelixM
A: 

Hi!

Here is the documention for exception in the session http://nhforge.org/doc/nh/en/index.html#best-practices

cws