I'm trying to check if SERIALIZABLE isolation level should fit the following case: table events contains records of events scheduled for given date and time for a specific user. Service should check the absence of already scheduled events for given user and time frame, and if no events exist to create a new event (upsert operation).
#Transactional(isolation = Isolation.SERIALIZABLE)
#Override
public Event createEvent(Event event) {
eventRepository.checkIfNoEventExists(event);
...
// load entities from different tables and inject to event
...
eventRepository.save(event);
}
Would this transaction block other concurrent transactions that perform write operation to other tables? What is the best practice to handle failure:
could not serialize access due to read/write dependencies among transactions
Would it be better to limit the scope of SERIALIZABLE isolation level to just check and save (e.g. in custom repository method)?
Related
I'm trying to avoid multiple row insertion with the same Identifier field value into the database. Here is the code:
public async Task InsertTransaction(Transaction transaction)
{
await using var dbTransaction = await _dbContext.Database.BeginTransactionAsync(IsolationLevel.RepeatableRead);
var existing = await _dbContext.Set<Transaction>()
.AsNoTracking()
.FirstOrDefaultAsync(t => t.Identifier == transaction.Identifier);
if (existing != null) {
return;
}
_dbContext.Set<Transaction>().Add(transaction);
await _dbContext.SaveChangesAsync();
await dbTransaction.CommitAsync();
return;
}
I'm assuming that RepeatableRead isolation level should be enough here, as it should lock for reading queries with search criteria containing Identifier and all requests after first one stepping into transaction will wait for it to finish.
However when running concurrent tests I'm getting multiple rows inserted with the same Identifier and things work properly only after changing transaction isolation level to Serializable.
I'm assuming that RepeatableRead isolation level should be enough here
No. REPEATABLE READ doesn't use key range locks for non-existent data. SERIALIZABLE is the only isolation level that does. Although SERIALIZABLE will take Shared (S) range locks, and so after multiple sessions hold the same S lock, the conflicting inserts will create a deadlock, instead of blocking the second session at the SELECT query.
I'm getting multiple rows inserted with the same Identifier
In addition to an insert-if-not-exists transaction, you should also have a unique index on Identifier to prevent duplicates.
In SQL Server the lock hints to use are updlock,holdlock, which force restrictive update (U) locks, and key range locking. So something like:
public bool Exists<TEntity>(int Id) where TEntity: class
{
var et = this.Model.FindEntityType(typeof(TEntity));
return this.Set<TEntity>().FromSqlRaw($"select * from [{et.GetSchema()??"dbo"}].[{et.GetTableName()}] with(updlock,holdlock) where Id = #Id",new SqlParameter("#Id",Id)).Any();
}
You still need a transaction or the U locks will be released immediately, but the transaction can be at the default READ COMMITTED isolation level.
Quick (I think) question about how Entity Framework participates in a DTC Transaction.
If I create two DbContexts within the same distributed transaction, will data I save in the first context be available to subsequent queries via the second context?
In other words, within a DTC transaction: I fetch, change and save data via context1, then create context2 and query the same entities. Will the uncommitted data from context1 be available to context2?
That depends on the isolation level you're using.
If you were to enclose your DbContexts in a TransactionScope and specifiy IsolationLevel.ReadUncommitted, e.g.
var options = new TransactionOptions{ IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted };
using(var scope= new TransactionScope(TransactionScopeOption.Required, options))
{
... DbContexts here
scope.Complete();
}
Then your second query will be able to see the changes made by the first.
For other isolation levels they won't.
The default isolation level for SQL Server is Read Committed, but for TransactionScope is Serializable.
See http://www.gavindraper.co.uk/2012/02/18/sql-server-isolation-levels-by-example/ for some examples.
It appears that the second context can indeed see the changes committed by the first. We are using read committed isolation. I am essentially doing (pseudo code):
Start Distributed Transaction
Context1: Fetch data into entities
Context1: Change entities
Context1: Save entities
Context2: Fetch data into same entities as used context 1
Context2: Change entities
Context2: Save entities
Commit Distributed Transaction
When I fetch into context 2, I do indeed see the changes that were saved in context 1, even though they are not committed (via DTC).
I'm using Google App Engine with Java JPA.
The isolation level is Serializable inside transaction; Repeated Read outside transaction.
I search a lot of articles and everybody talks about behaviors between transactions, but no one mention about read within the same transaction.
Example :
/* data in User table with {ID=1,NAME='HANK'} */
BEGIN;
UPDATE User SET name = 'MING' WHERE ID=1;
SELECT name FROM User WHERE ID = 1;
COMMIT;
Result : Still {ID=1, NAME='HANK'}
My Questions:
Does Isolation level setting affect queries within the same transaction?
What is the rule with the same transaction?
Any queries done within the same transaction will be immediately visible to itself. In your example if you read row with ID of 1, you will see that it is updated. The difference is how other users are affected by your transaction. Depending on your isolation level the other user may:
Get blocked, the other user will wait until you commit / rollback
Read the data as it was before the transaction (snapshot isolation)
Read the data that is most up-to-date even without you committing (read uncommitted)
I'm just scratching the surface of isolation levels, there have been a lot of books written on the subject.
First I'd like to describe the mechanism of a locking solution I'd like to implement. Basically an item can be opened in read or write mode. However if an user opens the item in write mode, no other user should be able to open it in edit mode. The item means a case in a customer service application.
In order to to this I came up with the following: The table will contain a flag which indicates if an item is checked out for edit, and an 'end time', while this flag is valid. The default value for it is 3 minutes, if no user interaction happens during this time, the flag can be ignored next time when an user tries to open the same item.
On the UI side, I use jQuery to monitor if an user is active. If he or she is, a periodic AJAX call extends his or her time frame so he or she can continue working on the item. When the user saves the item, the flag will be removed. The end time is necessary to handle situations when the browser crashes or when the user goes to drink a coffee and leaves the item open for an hour.
So, the question. :) If an user opens the item in edit mode first I have to read the flag & time values for the time item, and if I find these valid (flag is not set, or set but not valid because of the time) and I have to update them with new values.
What kind of transaction level should I use for this in EF, if any? Or should I write stored procedures to handle the select & update in a transaction? If so, what kind of locking method should I use?
You are describing pessimistic locking, there is really no debate on that. There are detailed instructions on what you want to do in the excellent MVC/EF tutorial http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
There’s a chapter early on about pessimistic.
Optimistic locking is still OK in this case. You can use timestamp / rowversion and your flag together. The flag will be used to handle your application logic - only single user can edit the record and the timestamp will be used to avoid race condition when setting the flag because only single thread will be able to read the record and write it back. If any other thread tries to read the record concurrently and saves it after the first thread it will get concurrency exception.
If you don't want to use timestamp different transaction isolation level will not help you because isolation level doesn't force queries to lock records. You must manually write SQL query and use UPDLOCK hint to lock the record by querying and after that execute update. You can do this in stored procedure.
The answer below is not a good way to implement pessimistic concurrency. You should not implement this at the application level. The RDBMS have better tools for this.
If you are locking a row in the db, this is by definition pessimistic.
Since you are controlling the pessimistic concurrency at the application level, I don't think it matters which transaction scope EF uses. EF will automatically start a db-level transaction when you SaveChanges.
To prevent multiple threads from executing the lock / unlock from your app, you can lock the section of code that queries & updates like so:
public static object _lock = new object();
public class MyClassThatManagesConcurrency
{
public void MyMethodThatManagesConcurrency()
{
lock(_lock)
{
// query for the data
// determine if item should be unlocked
// dbContext.SaveChanges();
}
}
}
With the above, no 2 threads will ever execute code inside the lock section at the same time. However, I am not sure why this is necessary. If all you are doing is reading the object and unlocking it when time has expired, and 2 threads enter the method at the same time, either way, the item will become unlocked.
On the other hand, if your db row for this object has a timestamp column (not a datetime column but a columng for versioning rows), and 2 threads enter the method at the same time, the second will receive a concurrency exception. But unless you have are versioning rows at the db level, I don't think you need to do any locking.
Reply to comment
Ok I get it now, you are right. But you are still locking at the application level, which means it should not matter which db transaction ef chooses. To prevent 2 users from unlocking the same object, use the C# lock block I posted above.
I am using transactionscope to ensure that data is being read to the database correctly. However, I may have a need to select some data (from another page) while the transaction is running. Would it be possible to do this? I'm very noob when it comes to databases.
I am using LinqToSQL and SQL Server 2005(dev)/2008(prod).
Yes, it is possible to still select data from a database while a transaction is running.
Data not affected by your transaction (for instance, rows in a table which are being not updated) can usually be read from other transactions. (In certain situations SQL Server will introduce a table lock that stops reads on all rows in the table but they are unusual and most often a symptom of something else going on in your query or on the server).
You need to look into Transaction Isolation Levels since these control exactly how this behaviour will work.
Here is the C# code to set the isolation level of a transaction scope.
TransactionOptions option = new TransactionOptions();
options.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
using (TransactionScope sc = new TransactionScope(TransactionScopeOption.Required, options)
{
// Code within transaction
}
In general, depending on the transaction isolation level specified on a transaction (or any table hints like NOLOCK) you get different levels of data locking that protect the rest of your application from activity tied up in your transaction. With a transaction isolation level of READUNCOMMITTED for example, you can see the writes within that transaction as they occur. This allows for dirty reads but also prevents (most) locks on data.
The other end of the scale is an isolation level like SERIALIZABLE which ensures that your transaction activity is entirely isolated until it has comitted.
In adition to the already provided advice, I would strongly recommend you look into snapshot isolation models. There is a good discussion at Using Snapshot Isolation. Enabling Read Committed Snapshot ON on the database can aleviate a lot of contention problems because readers are no longer blocked by writers. Since default reads are performed under read commited isolation mode, this simple database option switch has immediate benefits and requires no changes in the app.
There is no free lunch, so this comes at a price, in this case the price being aditional load on tempdb, see Row Versioning Resource Usage.
If howeever you are using explict isolation levels and specially if you use the default TransactionScope Serializable mode, then you'll have to review your code to enforce the more bening ReadCommited isolation level. If you don't know what isolation level you use, it means you use ReadCommited.
Yes, by default a TransactionScope will lock the tables involved in the transaction. If you need to read while a transaction is taking place, enter another TransactionScope with TransactionOptions IsolationLevel.ReadUncommitted:
TransactionScopeOptions = new TransactionScopeOptions();
options.IsolationLevel = IsolationLevel.ReadUncommitted;
using(var scope = new TransactionScope(
TransactionScopeOption.RequiresNew,
options
) {
// read the database
}
With a LINQ-to-SQL DataContext:
// db is DataContext
db.Transaction =
db.Connection.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);
Note that there is a difference between System.Transactions.IsolationLevel and System.Data.IsolationLevel. Yes, you read that correctly.