Telerik OpenAccess Classic

Telerik OpenAccess ORM Send comments on this topic.
Concurrency Anomalies
Programmer's Guide > OpenAccess ORM Classic (Old API) > Programming With OpenAccess > Concurrency Control > Concurrency Anomalies

Glossary Item Box

This documentation article is a legacy resource describing the functionality of the deprecated OpenAccess Classic only. The contemporary documentation of Telerik OpenAccess ORM is available here.

Weakening the isolation property of the ACID principle implies that database inconsistencies can occur when more than one transaction is working concurrently on the same objects. In the space of time between when objects are read and then written, the same objects can be read from the database and even manipulated by other transactions. This leads to concurrency anomalies.

Depending on the kind of operations and the order in which they are executed, various kinds of concurrency anomalies can occur. The following lists some typical examples.

Dirty Read

A dirty read occurs if one transaction reads data that has been modified by another transaction. This results in a violation of transaction isolation, if the transaction that modified the data is rolled back. In OpenAccess ORM, a write operation is first performed in memory only. The changes are not flushed to the backend before a commit or flush is executed. This itself does not prevent dirty reads, but lowers the risk. Whether dirty reads are actually avoided or not depends on the database backend used and/or its configuration (e.g., the isolation level that is set).

Lost Update

A lost update occurs when two transactions read the same object and then modify this object independently. The transaction that is committed last overwrites the changes made by the earlier transaction.

Concurrency Anomaly—Lost Update

Transaction1 changes the Name of the customer having ID 007 while Transaction2 changes the DiscountRate of the same customer. The GetCustomerById() method returns the customer with a given name. The implementation is not shown here.

Transaction1

Transaction2

scope1.Transaction.Begin()

scope2.Transaction Begin()

Customer c = GetCustomerById( 7 );

Customer c = GetCustomerById( 7 );

c.Name = Cooper;

c.DiscountRate = 1.5;

scope1.Transaction.Commit();

 

 

scope2.Transaction.Commit();

In this example, Commit() issued by Transaction2 overrides the changes made by Transaction1; i.e., the DiscountRate for the customer is changed but the name of the customer remains unchanged. In other words, the changes made by Transaction1 are lost.

Non-repeatable Read

A non-repeatable read occurs when a object is read twice within a transaction; and between the reads, it is modified by another transaction, therefore, the second read returns different values as compared to the first; i.e., the read operation is non-repeatable. OpenAccess ORM uses in-memory copies of database objects. Once an object is loaded into memory, there is no need to fetch it from the database each time a member is accessed. A new read operation, and therefore a non-repeatable read, could only occur when an application explicitly refreshes an object. This depends on the backend and/or its configuration (e.g., if it is a "versioning" database that maintains multiple versions of a row for concurrently running transactions).

Concurrency Anomaly—Non-repeatable Read

Transaction1

Transaction2

scope1.Transaction.Begin()

scope2.Transaction Begin()

Customer c = GetCustomer( 7 );

Customer c = GetCustomer( 7 );

 

c.Name = Clark;

 

scope2.Transaction.Commit();

scope1.Refresh( c );

 

c.Name is now "Clark"

 

scope1.Transaction.Commit();

 

In this case, the name of Customer c changes when the transaction reads the instance from the database again by calling Refresh().

Reading Inconsistent States

This is a generalization of non-repeatable read and refers to a violation of a constraint. For example, there could be a constraint that the DiscountRate for the customers with IDs 2 and 4 should always be the same.

Concurrency Anomaly—Reading Inconsistent States

Although a transaction might not violate this constraint by itself, another transaction might receive a pair of values for customers 2 and 4 where this constraint is violated. See the following sequence of operations. Assume that the DiscountRate for both customers is initially 1.0.

Transaction1

Transaction2

scope1.Transaction.Begin();

scope2.Transaction.Begin();

Customer c2 = GetCustomer( 2 );

 

scope1.Refresh( c2 );

Customer c2 = GetCustomer( 2 );

c2.DiscountRate is 1.0

c2.DiscountRate = 1.2;

 

Customer c4 = GetCustomer( 4 );

 

c4.DiscountRate = 1.2;

 

scope2.Transaction.Commit();

Customer c4 = GetCustomer( 4 );

 

scope1.Refresh( c4 );

 

c4.DiscountRate is 1.2

 

scope1.Transaction.Commit();

 

Transaction1 shows an inconsistent state in which the DiscountRates are different. Based on this inconsistent state, Transaction1 could make incorrect calculations.

Phantom Read

Phantom reads are of a totally different nature than the anomalies introduced previously. They can occur when a transaction defines a subset of data items that the transaction wants to work with; e.g., by performing a query and obtaining a query result. At this point, it is possible that data items are concurrently changed by another transaction so that they no longer qualify for inclusion in the query result, or vice versa. The same applies to objects that are inserted or deleted.

Concurrency Anomaly—Phantom Read

Assume that there are a number of employees who work for the same supervisor. All employees whose supervisor is John Smith should share a fixed amount per annum salary increase. Thus a program first calculates the number of employees working for John Smith, divides the amount to share by the number of employees and iterates over all employees adding one share.

If another employee becomes a member of John Smith's department after the number of employees was obtained, but before the salary increase takes place, more money than planned is distributed.

Copy Code
string fromWhereClause =

  "FROM EmployeeExtent AS e " +

  "WHERE e.boss.firstname=\"John\" AND e.boss.lastname=\"Smith\"";

(GetEmployee() is a user-defined method for returning a particular Employee instance.)

Transaction1

Transaction2

scope1.Transaction.Begin();

scope2.Transaction Begin();

IQueryResult countResult = scope1.GetOqlQuery( "SELECT COUNT(*) " + fromWhereClause ) ).Execute();

Employee johnSmith = GetEmployee( "John", "Smith" );

int count = (int) countResult.Get( 0 );

Employee e = GetEmployee( "Will", "Bean" );

float increase = amount / count;

e.boss = johnSmith;

 

scope2.Transaction.Commit();

IQueryResult result = scope1.GetOqlQuery( "SELECT * " + fromWhereClause ) ).Execute();

 

result size is > count

 

foreach ( Employee emp in result )

 

// increase salary

 

 

 

scope1.Transaction.Commit();