Skip to main content

skip to main content

developerWorks  >  Information Management  >

Yet Another Implementation of Optimistic Locking in DB2 UDB EEE V7.2

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


New site feature

Check out our new article design and features. Tell us what you think.


Rate this page

Help us improve this content


Level: Introductory

Alexander Kuznetsov, Certified Advanced Technical Expert , IBM

17 Sep 2002

Preventing lost updates may be challenging. This article describes a simple and very efficient solution to this problem using triggers and SQL stored procedures to generate unique numbers in the middle tier.

© 2002 International Business Machines Corporation. All rights reserved.

Introduction

Let's suppose you want to rent a car for a trip someplace where it is snowing. Because of the snow, you specify both traction control and ABS as mandatory conditions in your search criteria. While you are browsing a list of cars available for rent, a rental agency clerk is correcting a data entry error for the same car -- the car you have just chosen does not actually have traction control. By the time you click Reserve, the error in the database has been corrected, but you have, unknowingly, chosen a car without traction control.

This article describes just one simple approach to solve this problem, also known as the "problem of lost updates." This article does not attempt to provide any comparisons of different solutions to this problem; it describes a simple, efficient and scalable method of generating unique numbers in the middle tier.



Back to top


Using unique record version numbers to prevent lost updates

A detailed analysis of the problem of lost updates may be found in [1]. Let's go through the scenario described in the introduction and consider another approach to solving it.

The data on rental cars is stored in a table. The table is populated with sample data:

 
CREATE TABLE CAR(PART_KEY SMALLINT NOT NULL, 
	ID INT NOT NULL, 
	MAKE_MODEL VARCHAR(100) NOT NULL, 
	TAG CHAR(9) NOT NULL, 
	HAS_ABS CHAR(1) NOT NULL DEFAULT 'Y', 
	HAS_TRACTION_CONTROL CHAR(1) NOT NULL DEFAULT 'N', 
	RESERVED_FOR VARCHAR(100), 
	VERSION_ID INT NOT NULL DEFAULT 1, 
PRIMARY KEY(PART_KEY, ID)); 
INSERT INTO CAR 
(PART_KEY, ID, MAKE_MODEL, TAG, HAS_TRACTION_CONTROL, VERSION_ID ) 
VALUES (1, 1, 'Chevrolet Prizm LSI 1998, White', 'RENT ME NOW', 'Y', 1);

Having specified a search criteria, WHERE HAS_TRACTION_CONTROL = 'Y' AND HAS_ABS = 'Y' in my Web browser, I sent my request to the server. In response to my request, the server had returned a result set and completely forgot about my request, which means no cursors were left open, and all the locks had been released. Yet, every record sent to my Web browser was provided with a unique record version number, VERSION_ID. Record number 1 satisfied my search criteria and was retrieved along with its version number, 1. (Version number 1 is unique across the whole table, which means no other record will ever have version number 1). The clerk retrieved the same record, did a fast update, and saved the changes. To indicate that the record had been updated, the middle tier generated a new version number for this record, 2.

 
UPDATE CAR SET 
	HAS_TRACTION_CONTROL = 'N', 
	VERSION_ID = 2 
	WHERE ID = 1 
	AND VERSION_ID = 1; 

This update succeeded. Note the condition AND VERSION_ID = 1 in the WHERE clause. Our approach is either to update the same version that was retrieved or to fail. We don't want to overwrite somebody else's changes. For this schema to work correctly, we must provide a different record version number every time a record is updated. Let's create a BEFORE UPDATE trigger to guarantee that every updated record gets a different version number:

 
CREATE TRIGGER CAR_UPD_CHECK 
	NO CASCADE 
	BEFORE UPDATE ON CAR 
	REFERENCING NEW AS NEW_ROW OLD AS OLD_ROW 
	FOR EACH ROW 
	MODE DB2SQL 
WHEN (NEW_ROW.VERSION_ID = OLD_ROW.VERSION_ID) 
SIGNAL SQLSTATE '80000' 
('An attempt to update without providing a different version number') 
@ 

When I finally made up my mind and clicked Reserve, the middle tier generated a new record version number, 3:

 
UPDATE CAR SET 
  RESERVED_FOR = 'Alexander Kuznetsov', 
  VERSION_ID = 3 
	WHERE ID = 1 
	AND VERSION_ID = 1; 
 
SELECT * FROM CAR; 

The update failed, as it should, because the version number of the record did not match (DB2 returned SQLSTATE '02000' No row was found for FETCH, UPDATE or DELETE).

The most important advantage of this approach is its simplicity. Also the performance impact of adding a very simple trigger is minimal. An existing index on ID is used for the updates (it is created for the primary key constraint), so there is no need to create additional indexes.

As we have seen, it is possible to prevent lost updates using very simple means. In a perfect world we would stop right at this place. However, in a real world there is another nasty possibility: the record you are going to update may be already deleted. Another one with exactly the same primary key value may be inserted in its place by the time you finally decide to save the record. Since neither sequences nor idenities are available in EEE, this possibility is quite real. So let's make sure every version of every record has a version number unique across the whole table. That done, the newly inserted record will have a different version number. The same trigger that we have discussed before will detect this situation just as well. Now let's discuss how to generate unique numbers in the middle tier.



Back to top


Generating unique version numbers

If you are planning to have an informal party at home, and you suddenly realize you don't have any plastic cups, you might drive to the nearest grocery store and pick up a reasonable amount. Perhaps 50, 100 or 250; they don't take much room in the trunk anyway. Plastic cups aren't perishable. If any are left, they will certainly be used later. If you have miscalculated and need to replenish the supply, you will probably make another trip to get another hundred or so. You wouldn't be making a round trip to the store to buy only one cup each time a guest arrived, would you? Well, me neither, even if the car runs great, the road is smooth and there isn't much traffic.

For our scheme to work, we need to provide unique version numbers for every version of every record. If we are using DB2 UDB EEE V7.2, we do not have an option of using a sequence. Several approaches to generating unique numbers in this situation are described in [2]. Let's consider another one: a JavaTM class retrieves the unique numbers that are generated by the stored procedure and provides them to other middle tier modules. Sample Java code is provided in Resources.

To get a unique number from a sequence implemented in DB2, a middle tier process sends a request via network. The request is then processed by a server running DB2. Then the result is returned via network. So, retrieving a unique number from a sequence comes at a price of a round trip over the network and the overhead of a database call as shown in Figure 1.


Figure 1. Retrieving single unique numbers
Retrieving single unique numbers

Our implementation is somewhat different (Figure 2).


Figure 2. Retrieve intervals of unique numbers
Retrieving intervals of unique numbers

A middle tier process requests a wholesale amount of unique numbers, for example 100. A stored procedure reserves a contiguous interval of unique numbers, say from 201 to 300, and returns the ends of the interval, a pair of numbers (201, 300). Now this middle tier process may consume these unique numbers by itself or provide them to other processes or both ... It's no longer any burden for the server or the network. It's 100 unique numbers at a price of just 1. (Well, not exactly. A custom implementation based on a stored procedure cannot possibly outperform a sequence that comes with DB2). Another middle tier process requests and receives the next wholesale amount of unique numbers, say 1000, from 301 to 1301. Generating 1000 unique numbers is as easy for both the server and the network as generating just 100. As unique numbers are generated, a record in a database is being updated and the updates are committed every time.

As we have seen, it's more efficient to retrieve intervals of unique values (Figure 2) rather than get every value from the servers (Figure 1), because:

  • Some workload is moved from the servers to the middle tier.
  • Network traffic is significantly reduced.

Implementing this approach is quite simple. Let's create a table. In this example PART_KEY is a partitioning key by default:

 
CREATE TABLE AVAILABLE_NUMBER 
(PART_KEY SMALLINT NOT NULL, 
AVAILABLE_NUMBER INT NOT NULL); 

Now let's populate the table:

 INSERT INTO AVAILABLE_NUMBER(PART_KEY, AVAILABLE_NUMBER)VALUES(0, 0); 

Here's the stored procedure that generates intervals of unique numbers:

 
CREATE PROCEDURE DB2INST2.GET_UNIQUE_VALUE_EXTENT 
	( IN NUM_VALUES INT, 
	OUT VALUES_START INT, 
	OUT VALUES_END INT, 
	OUT SQLSTATE_OUT CHAR(5), 
	OUT SQLCODE_OUT INT) 
LANGUAGE SQL 
P1: BEGIN ATOMIC 
DECLARE SQLSTATE CHAR(5) DEFAULT '00000'; 
DECLARE SQLCODE INT DEFAULT 0; 
DECLARE UNDO HANDLER FOR SQLEXCEPTION 
BEGIN 
 
-- You must extract both  SQLSTATE and SQLCODE in one statement 
SELECT SQLSTATE, SQLCODE 
	INTO SQLSTATE_OUT, 
	SQLCODE_OUT 
FROM SYSIBM.SYSDUMMY1; 
END; 
 
-- Setting default values to output parameters 
SET VALUES_START=0; 
SET VALUES_END=0; 
SET SQLSTATE_OUT=SQLSTATE; 
SET SQLCODE_OUT=SQLCODE; 
 
-- First let's update the record. 
-- Because the UPDATE statement is inside an ATOMIC block, an update lock 
-- on the record is held until the transaction is committed. 
UPDATE AVAILABLE_NUMBER SET AVAILABLE_NUMBER = AVAILABLE_NUMBER + NUM_VALUES; 
 
-- It's assumed there is only one record. 
-- The statement will fail if there are multiple records. 
SELECT AVAILABLE_NUMBER INTO VALUES_END FROM AVAILABLE_NUMBER; 
SET VALUES_START = VALUES_END - NUM_VALUES; 
 
-- The client will COMMIT the transaction and release the update lock 
END P1 

Users are not granted any rights on the AVAILABLE_NUMBER table, they only have EXECUTE privileges on the stored procedure. The SQL UPDATE statement in the stored procedure must acquire an update lock -- only then an interval of values is retrieved and returned to the middle tier. Another call of this stored procedure must wait to acquire its update lock. This guarantees the uniqueness of generated values, no matter how many connections retrieve them. Note that when AVAILABLE_NUMBER exceeds its maximum positive value, an error is raised and no more values are generated.



Back to top


Conclusion

As we have seen, it's possible to implement a simple algorithm to avoid "lost updates." The price of doing this, of course, is adding a column and an update trigger on every table being protected. Also we have seen the generation of unique numbers in the middle tier and discussed the advantages of this approach.



Back to top


Acknowledgements

The author would like to thank Serge Rielau for a very useful discussion. The author would also like to thank Anna Krylova, his wife, and Elden Bastian, his friend, for their help and encouragement.

IBM, DB2, DB2 Universal Database are trademarks or registered trademarks of IBM Corporation in the United States, other countries, or both.

Java and all Java-based trademarks and logos are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States, other countries, or both.

IBM copyright and trademark information




Back to top


Downloads

DescriptionNameSizeDownload method
Sequence samplesequencesample.zip3KBHTTP
Sequence samplesequencesample.tar10KBHTTP
Information about download methods


Resources



About the author

Alexander Kuznetsov has 14 years of experience in software design, development and database administration. Currently he designs a multi-terabyte clustered database in DB2 UDB EEE . Alexander is an IBM Certified Advanced Technical Expert (DB2 Cluster) and an IBM Certified Solutions Expert (Database Administration and Application Development). He can be reached at alkuzo at mindspring.com and the comp.databases.ibm-db2 newsgroup




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top