NH Prof AlertsUse 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.
More posts in "NH Prof Alerts" series:
- (31 Dec 2008) Use statement batching
- (30 Dec 2008) Too many database calls per session
- (29 Dec 2008) Excessive number of rows returned
- (29 Dec 2008) Unbounded result set
- (28 Dec 2008) Use of implicit transactions is discouraged
- (28 Dec 2008) Select N + 1
Comments
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.
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?
Steve,
Can you expand more about why you found this confusing and how I can make this less so?
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.
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.
"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.
Not really.
When you Complete() the TransactionScope, NH will handle this for you
Isn't TransactionScope support only since NH 2.1?
Jan,
Yes & No.
It sort of works in 2.0, if you don't do things that break it.
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?
Using DTC is costly, and I wouldn't recommend it unless you are truly orchestrating several resources.
In the code:
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction())
{
}
Won't this disallow for lazy loading because the session will be gone when it leaves the scope of the "using"?
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.
Comment preview