Gavin King has written a nice piece on how locks in the middle tier can be very bad things. He discusses how various Java persistence frameworks try to implement their own locking, and how they should probably let the database do this work.
"Unfortunately, some Java persistence frameworks (especially CMP engines) assume that they can improve upon the many years of research and development that has gone into these relational systems by implementing their own concurrency control in the Java application. Usually, this takes the form of a comparatively crude locking model, with the locks held in the Java middle tier. There are three main problems with this approach. First, it subverts the concurrency model of the underlying database. If you have spent a lot of money on your Oracle installation, it seems insane to throw away Oracle's sophisticated multiversion concurrency model and replace it with a (less-scalable) locking model. Second, other (non-Java?) applications that share the same database are not aware of the locks. Finally, locks held in the middle tier do not naturally scale to a clustered environment. Some kind of distributed lock will be needed. At best (for example, in JBoss), distributed locking will be implemented using some efficient group communication library like JGroups. At worst (for example, in OJB), the persistence framework will persist the locks to a special database table. Clearly, both of these solutions carry a heavy performance cost. Accordingly, Hibernate was designed to not require any middle-tier locks - even thread synchronization is avoided. This is perhaps the best and least-understood feature of Hibernate and is the key to why Hibernate scales well. So why do other frameworks not just let the database handle concurrency?"
Read Gavin King in Don't lock in the middle tier!
Locking doesn't belong in the backend
or the middle tier because it is only
the application that knows what should
be locked when and for how long.
If i am updating application objects,
what use is locking in the database?
Sometimes you can't wait for user response in transaction. In Web application it is almost impossible to keep database transaction between two HTTP requests. Then you will split database processing in two or more transactions. For instance load transaction and save transaction. In this case application has to take care of some concurrent issues. Generally, database transactions are not answer to concurrent editing.
That's what version stamps and optimistic concurrency is for... let Hibernate fingure out that someone changed the data between the time you read it and when you want to save your edits... Catch the exception and start over.
But before allowing optimistic concurrency, catching exceptions and starting over, wouldn't it be nice to ask users, if they are prepared to loose their work and enter data again. The thing is, that pessimistic locking (i know, i know it's bad, but still exist) is often dictated by users, and thats where middle tier locking comes handy.
I am faced with shifting an app from a client server to web-based application. Currently users make notes in the application while online with clients. Oftentimes, the intake people may still be finishing up their notes after passing a case off to other CSR's who will want to update the same record sometimes at the same time. Reserving the record is therefore absolutely essential - currently achieved by hitting an 'edit' button. I have been wondering how to do that in Hibernate.
In an application I am developing, there is a complex editor implemented in an applet. Users may sped more then hour in an editing session. I generally use optimistic locking in the application, but in this case application has to forbid two users to edit same object in the same time. Moreover, if you try to edit locked object, application should tell you who use object and from what computer. So, you can go to his/her office to see what is going on.
BTW, in this application I use custom XDoclet based DAO generator, but use Hibernate in some newer projects.
We might want to just admit that locking
is very difficult because the semantics
are so different from application to
application. Any attempt to make one policy
fits all won't work.
I Absolutely agree!
That's why I firmly believe that a decent O/R tool should allow the user to select his locking strategy of choice or even be allowed to plugin his own strategies.
That's what we are aiming at Apache OJB. We don't want to lock in users to only one concept. There is no one concept that fits for all purposes.
The problem you describe has nothing to do with locking. Your DB is modeled incorrectly. User's shouldn't update the same record. Some of the best DB designs do not allow UPDATES of records. Instead they rely on INSERTS of new records that are associated with each other.
The record locking debate has been around for a long time and I completely disagree with locking anything. The only situation I have ever seen any need for any sort of locking is a batch process.
Well, if A.A.'s customer requires LOCK (pessimistic !) for EDIT,
than argument that Race Condition does not like
locking is...well...funny! Lock must be set, and no
other soul must have possibility to UPDATE or INSERT new
version (implementation is not important here).
Yes, locking is hard, bad, bottle-neck, headache, so what?
RC, You are lucky, that you can change mind of customer
and his business requirements.... Very lucky! :-)
<Andrei_Tokar>>The thing is, that pessimistic locking (i know, i know it's bad, but still exist) is often dictated by users, and thats where middle tier locking comes handy.</Andrei_Tokar>
Thats all very well but in many cases this just does not scale (no matter what the users say), so unless you have the hardware that supports you user base with persimistic concurrency you will have to find a way around this. Remember the benefit of optimistic concurrreny is that if that has been no conflict you did not waste resources through locking for no reason. When a conflict does occur you take the hit of lossing the data in the update.
Also there are things you can do by factoring you long lived transactions in a way that reduces the need for long lived database tranactions persimitic or optimistic. These issues have been discussed on TSS before but I cound not find the article.
Thats all very well but in many cases this just does not scale
>> (no matter what the users say),
I can not see why it won't scale. You are not wasting any resources here. I think that title of the post is a litle bit misleading. There is no long transactions whatsoever. Just a flag in app which prevents other users from accessing this particular bussines object for editing. It may present a problem as asingle point of failure if not replicated in a cluster, or there is a risk of concurrent updates at database level, but scalability...
Please read the Martin Fowler "Pessimistic Offline Lock". He explains the pro and con of what you call "a simple flag" and I don't like repeating it here. This and Gavin's blog entry gives a good overview when and when not to consider it.
Basically, if you keep the flag in the database (which is the easiest way to make it cluster-safe), you have to manage the lock (table) with a serializable database transaction. This is a bottleneck in high concurrency systems, all threads have to go through a (short) serializable transaction to the databse. With good design and some thought it can easily be optimized, but it won't be as fast as an optimistic approach. I'll probably have some real numbers soon...
IMHO, pessimistic loc with "flag"
(e.g., EMPLOYER_ID, TIMESTAMP, VERSION_ID, etc)
can be set and released as _atomic_ transactions
(at least on single table).
Where did you find need for "serializable" mode here?
Thanks in advance.
A transaction is by definition atomic. If you manage a lock table, how do you ensure that two concurrent transactions don't get uncommited data or phantom reads when setting or releasing locks? By using a serializable transaction isolation mode.
Hmmm... may be we use different terminology.
I use "Atomic" for single database operation,
for example single SQL statement, that can not be
interapted by another thread, for example this set lock:
Update TX_RECORD_TABLE set
...EMPLOYER_ID = :my_id,
...TIMESTAMP = sysdate
where RECORD_ID = :rec_id
...and EMPLOYER_ID = NULL
All business updates can be of the following type:
Update TX_RECORD_TABLE set
...SALARY = :new-salary
where RECORD_ID = :rec_id
....and EMPLOYER_ID = :my_id
Of cource, it must be a single database
place where lock must be set for whole business
domain object (if there are several tables per object).
P.S. Yes, "transaction" is "atomic" also by hard work of
some Transaction manager, not by itself.... :-)
In above example, assume EMPLOYE_ID is
ID of operator who EDITs this record, not
employer's from the record
I don't understand what a "business update" is, but I think you are talking about exclusive read or exclusive write locks only. It is possible to implement these locks with a single SQL statement for aquiring, checking and releasing the lock, having a UNIQUE constraint on the locked items ID. However, if you implement a read/write lock, which you probably need if multiple readers require the most recent data, you have SELECT and INSERT for one lock operation. This must be executed with the full serializable isolation.
"business update" = update on some business atribute, not
a set/release lock.
(just to remind myself what was initial discussion)
My position is that:
set of Pessimistic locks with "flags" can be done
as single atomic SQL. release of the lock can be
under ordinary READ_COMMITED isolation level
(no other transactions will pass EMPLOYER_ID=:my_id test).
Thus, pessimistic application "flags"
are not (by-themself) database bottle-neck in comparison
to optimistic strategies.
The normal sequence is: (i) start TX, (ii) select data
Any select BEFORE start of transaction is not garanteed
If any select is part of desigion to start transaction:
it can be placed in WHERE clause of lock-setting SQL.
P.S. Yes, I did assumed that UNIQUE :rec_id is known.
If not - there is WHERE clause, as described above....
How do you implement a read/write locking strategy with only single SQL statements for aquiring locks?
I consider only two states, open and locked.
Open is when EMPLOYER_ID is NULL -
everybody reads and Lock can be set
Locked is when EMPLOYER_ID is not NULL -
everybody reads (with understanding that data can be changed,
only owner of lock (:my_id) can update
This is single SQL case for aquiring a lock (as shown above)
May be you was asking about requirements for lock #3:
READ-LOCK (possible multiple ones),
that prevent from setting WRITE-LOCK (???)
I have no experience with this type. I guess parent-child
for multiple locks to object. Here we have INSERT with condition of
"existing" or "not-existing" of another records in the same table.
It would be hard to make it single SQL....
(((( just for fun:
insert into....values (x,y,z, 1/call_function))
where call_function returns count of locks for this object.
returning zero will kill insert - may be it is not what we want, but
this is kind of communication from "count other records")
A transaction is by definition atomic.
> If you manage a lock table, how do you ensure that
> two concurrent transactions don't get uncommited data or phantom reads
> when setting or releasing locks?
> By using a serializable transaction isolation mode.
Why would that be necessary? A "lock table" usually looks like this -
So, attempting to lock a record means
insert into lock_table values (12345);
Commit will either work (in which case the record is locked)
or it will report a constraint violation. Locking a record DOES NOT INVOLVE
reading from the lock_table at all. Where would you need
let Hibernate fingure out that someone changed the data between the time you
> read it and when you want to save your edits... Catch the exception and start
But wasn't the whole point of this exercise that the middleware should not do any locking (including Hibernate)? Or are we talking about pessimistic locking only?
Using optimistic concurrency control is not "locking" in the middle tier. And yes, you have several strategies to resolve a conflict with versioning:
1. Last commit wins: both updates succeed and the second update overwrites the changes of the first. No error message is shown.
2. First commit wins: the first modification is persisted and the user submitting the second change receives an error message. The user must restart the business process by retrieving the updated comment. This option is often called optimistic locking.
3. Merge conflicting updates: the first modification is persisted and the second modification may be applied selectively by the user.
An optimistic approach should be your first choice for high concurrency systems. Of course, you may use pessimistic locks for sensitive data/transactions. Preferred are locks held in the database (FOR UPDATE, etc.), as Gavin describes.
If you manage pessmistic locks in the application, you have a bottleneck. If done properly and only used for critical concurrent transactions, it may not be as bad as it sounds at first. However, it is certainly not recommended as the first solution for every concurrency problem.
BTW, Martin Fowler calls this "Offline Pessimistic Lock". It's easy to implement in Hibernate: A persistent class Lock that is managed by a LockManager SSB, using serializable transaction isolation for this SSB's transactions. This kind of locking should be the last option!
All this talk about "flexible" locking mechanisms, and there's "more than one way to do it" is really naive sophistry.
A DBMS has a tremendous ability to mediate among concurrent requests. Replacing this functionality with the equivalent of a tinker toy makes no logical sense, unless you or your framework providers really don't understand what the database offers. This is why I think Hibernate is the best free O/R toolkit out there: it understands the strengths of a database and realizes O/R mapping is about productivity. It's not about getting away from those gucky relations, it's about using them effectively.
Middle-tier caches are an exception, but you need to be very careful with the kind of data that goes into it.