Previous | Table of Contents | Next |
The TSEQUAL() Function
The last piece of the puzzle is provided by the TSEQUAL() function, which is used to verify that timestamp values have not changed (and hence the row has not been modified) prior to an UPDATE operation. You use tsequal() to compare the value of the timestamp stored in the row to the version provided when the row was initially read. Lets run through a brief example and then see how it works in a multiuser update situation.
You start by reading a row specified by the unique key, id. You need to retrieve the timestamp column as well.
select descr, qty, ts from ts_example where id = 3
descr qty ts ---- ------------ ------------------ lion 36 0×000000010000120c
When you update the row, you pass both the id and the timestamp value. The id is referenced directly in the WHERE clause to allow SQL Server to find the correct row in the table. On the other hand, the timestamp value should not be directly compared to the ts value online in the form,
<timestamp_column_name> = <timestamp_value>
as in
ts = 0×000000010000120c
If you try to compare them directly in this way and the timestamp had changed, SQL Server would fail to find the row, but no error message would be generated. In this example, Ill pass the wrong timestamp value, but use a direct comparison:
-- This is the wrong way to test a timestamp value update ts_example set qty = 38 where id = 3 and ts = 0×1111 -- the server will fail to find the row
(0 row(s) affected)
As you expected, the row was not found. No row matched the criteria (id = 3, ts = 0×1111). Although data integrity is preserved, no error message warns the user of a timestamp mismatch. To properly compare timestamps, use tsequal():
-- This is the proper way to test a timestamp value update ts_example set qty = 38 where id = 3 and tsequal(ts, 0×1111) -- the server finds the row but returns an error
Msg 532, Level 16, State 1 The timestamp (changed to 0×000000010000120c) shows that the row has been updated by another user.
The end result is the same as before: no rows were modified. On the other hand, the error message is extremely useful because it warns of an optimistic locking problem. The row was found according to the criteria (id = 3). Only after the row is found is the timestamp compared. That comparison causes the error message to be returned.
Figure 20.6 shows you how optimistic locking works in a multiuser update situation. Both users retrieve the row, obtaining the same data, including the same timestamp value, 0×1110. The first user to attempt an update will succeed because her timestamp value still matches the value stored online.
Figure 20.6. The timestamp value distinguishes different versions of a row.
Note: Dont be confused on this point. The order of retrieval does not matter at all. The first user to update the row gets through. Others, regardless of how long ago they originally read the row, will have an invalid timestamp value and any ultimate updates will fail.
The second user fails to update because the timestamp value she has (0×1110) does not match the one stored in the row (0×19c3).
Optimistic locking provides a useful method of managing data integrity in a multiuser environment. Its not very intrusive (there are no persistent locks); its scope is limited to a row, not a page; and it provides excellent data integrity.
Drawbacks to Optimistic Locking
There are two drawbacks to optimistic locking. First, users may be inconvenienced (perhaps furious?) when a complex row modification needs to be repeated because of an optimistic locking violation.
Note: You can write your application to improve usability after an optimistic locking violation. The program can compare the newly revised row to the original row to determine which columns have changed. If the modifications made by another user dont affect the modifications made by the current user of the program, you can re-execute the UPDATE statement.Updating requires a bit more code, but further reduces the chance that there will be a reason to frustrate the user. Updating when one users changes dont affect the current users changes works because normally one of the two updates is due to a program, updating a ytd_sales figure for instance, which the user doesnt care about. If the user only modifies the mailing address, their update should be allowed to happen on those fields.
Second, and this is quite serious, this method requires every application to participate. A single rogue user who performs updates without checking timestamp validity could wreak havoc on data integrity. To guarantee the reliability of optimistic locking, you would be wise to disallow direct updates using SQL and instead use stored procedures for all updates. The procedure could require a valid timestamp and always check tsequal() during the update process. Here is an example of a stored procedure that would handle a timestamp column properly:
create proc p_ts_example_update (@id int, @descr char(4), @qty int, @ts timestamp = null) as if @ts is null begin raiserror (A timestamp value is required, 16, 1) return 99 end declare @err int, @rc int update ts_example set qty = @qty, descr = @descr where id = @id and tsequal(ts, @ts) select @err = @@error, @rc = @@rowcount if @err <> 0 return 98 if @rc = 0 begin raiserror (Row not found: %d, 16, 1, @id) return 97 end return 0
To execute the procedure, you must pass a timestamp value.
declare @ret int exec @ret = p_ts_example_update 3, lion, 36 if @ret <> 0 print error in update else select * from ts_example where id = 3
Msg 50000, Level 16, State 1 A timestamp value is required error in update
The stored procedure requires the timestamp. If the wrong timestamp is passed, the timestamp error is generated. By using a stored procedure to manage all updates, you guarantee that no applications can access the table without passing a proper timestamp value.
Previous | Table of Contents | Next |