Ayende @ Rahien

Refunds available at head office

NH Prof Alerts: Use of implicit transactions is discouraged

This is a bit from the docs for NH Prof, which I am sharing in order to get some peer review.

A common mistake when using a database is that we should use only transactions to orchestrate several write statements. Every operation that the database is doing is done inside a transaction. This include both queries and writes ( update, insert, delete ).

When we don't define our own transactions, we fall back into implicit transaction mode, in which every statement to the database run in its own transaction, resulting in a higher performance cost (database time to build and tear down transactions) and reduced consistency.

Even if we are only reading data, we want to use a transaction, because using a transaction ensure that we get a consistent result from the database. NHibernate assume that all access to the database is done under a transaction, and strongly discourage any use of the session without a transaction.

Example of valid code:

using(var session = sessionFactory.OpenSession()) 
using(var tx = session.BeginTransaction()) 
{ 
	// execute code that uses the session 
	tx.Commit(); 
} 

Leaving aside the safety issue of working with transactions, the assumption that transactions are costly and we need to optimize them is a false one. As already mentioned, databases are always running in transaction. And databases have been heavily optimized to work with transactions. The question is whatever this is per statement or per batch. There is some amount of work that need to be done to create and dispose a transaction, and having to do it per statement is actually more costly than doing it per batch.

It is possible to control the number and type of locks that a transaction takes by changing the transaction isolation level (and indeed, a common optimization is to reduce the isolation level).

NHibernate treat the call to Commit() as the time to flush all changed items from the unit of work to the database, without an explicit Commit(), it has no way of knowing when it should do that. A call to Flush() is possible, but it is generally strongly discouraged, because this is usually a sign that you are not using transactions properly.

I strongly suggest that you would use code similar to the one shown above (or use another approach to transactions, such as TransactionScope, or Castle's Automatic Transaction Management) in order to handle transactions correctly.

Comments

Steve Bohlen
12/28/2008 08:35 PM by
Steve Bohlen

To me this is a (somewhat) confusing section of the docs without an assumption that a reader already has a good understanding of the difference between an NHibernate transaction and a database transaction.

Recognizing that this is bound to be a recurring theme with this product (you will need to assume SOME level of base NH understanding else your recommended 'solutions' to the problems pointed out by the NH Profiler won't be comprehensible to users of the software), I wonder if this (transaction mgt) is an area that might be worth extrapolating on a bit to provide some more background....?

I got what you were saying, but I'm uncertain that other more 'casual' NH users necessarily would.

My $0.02.

Andyk
12/28/2008 10:58 PM by
Andyk

Hi Oren,

in your last statement you recommend using x, y, z approaches. But does that mean then that you're now discouraging the unit of work pattern? Because you dont mention this at all?

Ayende Rahien
12/29/2008 05:58 AM by
Ayende Rahien

Steve,

Can you expand more about why you found this confusing and how I can make this less so?

Ayende Rahien
12/29/2008 06:00 AM by
Ayende Rahien

AndyK,

There is a difference between transactions and Unit of Work. A Unit of Work is usually composed of one or more transactions. This topic is focusing on something that is lower level than the unit of work.

Anders Lybecker
12/29/2008 08:17 AM by
Anders Lybecker

Hi Oren,

I agree, as long as the transactions are kept short. Otherwise it is better to use implicit transactions for reading.

Another note about the transaction isolation level: I wouldn't call it optimization to reduce the isolation level - you have to think upfront about the use of isolation levels.

In most cases when reducing isolation level, the cause is often nasty stuff like deadlocks etc.

Do consider readcommitted snapshot in SQL Server 2005+ when selecting transaction isolation level (similar to row versioning in the Oracle World which is default).

You should still keep your transactions short with readcommitted snapshot, otherwise you would be hammering the tempdb.

Jack
12/29/2008 02:23 PM by
Jack

"call to Flush() is possible, but it is generally strongly discouraged, because this is usually a sign that you are not using transactions properly."

This is true if you're using NHibernate's transaction manager, but if we're using TransactionScope, we need to explicitly called Flush.

Ayende Rahien
12/31/2008 02:46 AM by
Ayende Rahien

Not really.

When you Complete() the TransactionScope, NH will handle this for you

Jan Van Ryswyck
01/01/2009 06:37 PM by
Jan Van Ryswyck

Isn't TransactionScope support only since NH 2.1?

Ayende Rahien
01/01/2009 06:42 PM by
Ayende Rahien

Jan,

Yes & No.

It sort of works in 2.0, if you don't do things that break it.

Jan Van Ryswyck
01/01/2009 06:51 PM by
Jan Van Ryswyck

In that case, I did something that made NH angry :-). I'm thinking about your advice to use a NH transaction for both read and write operations. This makes complete sense to me.

However, I'm feeling a bit reluctant when it comes to TransactionScope as it can turn to distributed transactions without any transparent knowing from the developer. Using the DTC is rather costly I guess?

Ayende Rahien
01/01/2009 06:54 PM by
Ayende Rahien

Using DTC is costly, and I wouldn't recommend it unless you are truly orchestrating several resources.

Tim Barcz
01/02/2009 02:33 AM by
Tim Barcz

In the code:

using(var session = sessionFactory.OpenSession())

using(var tx = session.BeginTransaction())

{

// execute code that uses the session 

tx.Commit(); 

}

Won't this disallow for lazy loading because the session will be gone when it leaves the scope of the "using"?

Ayende Rahien
01/02/2009 08:00 AM by
Ayende Rahien

Yes, it would. And for a good reason.

You should manage your loading better. Transactions should be at the controller level, and scoped to include everything that goes on there.

Comments have been closed on this topic.