问题
I need to update a row in an Oracle database in a way that I don't silently clobber the changes from another client in my web based application.
In my current system I perform the following:
SELECT * FROM table WHERE id=:ID AND lastmodified=:LASTMOD
if the row still exists with the same last modified date when we started we know nobody has changed it so we can update using the last modified time.
However when doing this manually using two sessions I noticed if two clients select at roughly the same time its possible for one to miss that the row has been changed between the select and update step due to both occurring within the same second or millisecond.
The end result is that I clobber the changes of the other user and there is no warning that it occurred.
I was thinking of using SELECT FROM UPDATE but apparently that's a bad idea (especially for web apps), the article recommends re-reading (which is what I'm doing above) but I still think I'm at risk of a race condition.
Edit: Made it clear I was concerned about the way time is referenced.
回答1:
I'm assuming that your UPDATE
statement itself is verifying the lastmodified
value that you read in your SELECT
statement as ninesided suggests.
If lastmodified
is a DATE
, then there is a potential race condition if there are multiple updates to the same row in the same second since a DATE
only has granularity to the second. If lastmodified
is a TIMESTAMP
, on the other hand, the window in which the race condition can occur is much more limited since a TIMESTAMP
will have between 3 and 9 digits of sub-second precision (3 on most Windows machines, 6 on most Unix machines). It's pretty unlikely though not impossible that you'd have two updates at the same millisecond or even the same microsecond. But it's not infallible.
You can use a sequence-generated value instead of a last modified date. That can guarantee that you won't lose an update since a NOCYCLE sequence won't return the same value twice. But if you go down that path, you're either losing the information benefit of having a last update date on every row or you're storing a few extra bytes of data in every row of the table. Either of those trade-offs may be worth it depending on your application or either might create more problems than it resolves.
回答2:
one approach you could take is that when a user updates the table, they add
AND lastmodified = :LASTMOD
to the WHERE
clause of the update statement, where :LASTMOD
is the value returned by the user's original select. If the update affected now rows (SQL%ROWCOUNT=0
) then you know that a second user has updated that row since the first user originally ran their select.
回答3:
I'm not sure about an SQL internal solution, but I assume you're using some scripting language (php? perl?) to perform the web app/backend stuff, so you could simply use these to use a file lock or semaphore/mutex to lock writing and wait (if locked) or simply tell the user to wait a moment and try again.
As an alternative, you could have a look at the MediaWiki source. I know they've got a protection to avoid two users overwriting each other's content in the DB. And to be honest, I really think that Wikipedia would be quite a huge example of a platform, where such concurrent editing might happen more than once in a while.
回答4:
Another choice would be to enable ROWDEPENDENCIES
on the table (which requires rebuilding the table!) and use the ORA_ROWSCN
pseudocolumn. Tracking the SCN at a row- versus block-level costs 6 bytes per row; however, that's smaller than either a DATE
or a TIMESTAMP
column, and requires no additional objects to be created - like a sequence, or a trigger to make sure the sequence is populated.
For more, see Tom Kyte's asktom question here.
来源:https://stackoverflow.com/questions/8498169/race-condition-between-select-and-update