Reputation: 458
I am currently working with transactions and getting confused. These transaction are created at the data access layer and not in the stored procedures of the database (SQL Server 2008). I understand the normal working of Isolation Levels set for a transaction. I am unable to comprehend what should happen in the following scenario.
There are multiple threads doing the same thing but different ID. But there might be a case where in two threads look up the same ID. Lets call them Thread A and B. The above steps progress in the following manner with respect to the two threads. The Isolation Level is set to Repeatable Read.
A1. Initiate a transaction A2. Select Employee with ID=1. B1. Initiate a transaction B2. Select Employee with ID=1. A3. Update Employee with ID=1. A4. Commit B3. Update Employee with ID=1. B4. Commit
What I really want to achieve from the transaction is that when Thread A selects a particular record, Thread B should not even be able to select that record. I don't know if I am thinking on the right track by using transactions and locks for this scenario.
Awaiting replies :)
Upvotes: 3
Views: 11267
Reputation: 24410
Try something like this:
using System;
using System.Transactions;
using System.Data;
using Microsoft.Practices.EnterpriseLibrary.Data;
namespace StackOverflow.Demos
{
class Program
{
static Database db = DatabaseFactory.CreateDatabase("demo");
public static void Main(string[] args)
{
TransactionOptions options = new TransactionOptions();
options.IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead; //see http://www.gavindraper.co.uk/2012/02/18/sql-server-isolation-levels-by-example/ for a helpful guide to choose as per your requirements
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, options))
{
using (IDbConnection connection = db.CreateConnection())
{
connection.Open(); //nb: connection must be openned within transactionscope in order to take part in the transaction
IDbCommand command = connection.CreateCommand();
command.CommandType = CommandType.Text;
command.CommandText = "select top 1 status from someTable with(UPDLOCK, ROWLOCK) where id = 1"; //see http://msdn.microsoft.com/en-us/library/aa213026(v=sql.80).aspx
string statusResult = command.ExecuteScalar().ToString();
if (!statusResult.Equals("closed",StringComparison.OrdinalIgnoreCase))
{
command.CommandType = CommandType.Text;
command.CommandText = "update someTable set status='closed' where id = 1";
}
scope.Complete();
}
}
}
}
}
ps. generally it's recommended that you use stored procedures over hardcoded SQL as I've done above - if you can push all of your logic into the stored procs such that you just make one call to the proc and all logic's handled within the database go that way.
In the above example you'll notice the namespace:
Microsoft.Practices.EnterpriseLibrary.Data;
That's there because I tend to use MS's Data Block from their Enterprise Library, which gives you a load of functionality on top of the OOTB libraries. If you're interested you can read more on that here: http://msdn.microsoft.com/library/cc467894.aspx
Upvotes: 1
Reputation: 9639
You should use an UPDLOCK table hint to prevent deadlocks, e.g.,
select * from employee with (updlock) where id = @id
update employee set name = @name where id = @id
Without this you could get deadlocks, because the select by default takes a shared read lock:
So transaction A and B are now waiting for each other - classic lock escalation deadlock. The UPDLOCK table hint avoids this because it forces the select to take an exclusive lock:
Edit: You can combine the UPDLOCK with a ROWLOCK to ask for a row level lock, e.g., "with (updlock, rowlock)". You can ask, but you may not always get it - see http://msdn.microsoft.com/en-us/library/ms187373(v=sql.100).aspx. Also row locks can be more expensive than page locks, because SQL Server will probably have a lot more locks to track if you use row locks. So I would let SQL Server choose for itself the extent of the lock, it normally does an OK job; in this case it shouldn't take a table lock. Only explicitly use a rowlock if you have a problem without it.
Also note that a rowlock on its own won't prevent a deadlock where two transactions select the same record (row) and then try to update it - so you always need an updlock for this.
Upvotes: 5
Reputation: 419
I seems to me you should be looking at threading mechanism you're using. You should be able to know upfront (not during transaction) and not start a thread with the ID that's already being processed. Or youre threads should have access to some shared synchronized list with ID's that should be processed. That way two threads cant work on the same ID.
Upvotes: -1
Reputation: 27934
You should have a look at optimistic locking, it works by adding a extra check on the update where you check if the record is not changed between the read and the write. You can also read your record outside of your transaction scope, which gives you better performance overall.
Optimistic_concurrency_control wikipedia
Upvotes: 1