we have a problem to use TransactionScope. TransactionScope get to us very good flexibility to use transactions across our Data Access Layer. On this way we can use transactions implicit or explicit. There are some performance boost again ADO.NET transactions, but at this time this is not really problem. However we have problem with locking. In example code below, although isolation level is set to ReadCommitted, it is not possible to make Select SQL statement from other client on table testTable, until the main transaction (in Main method) will be committed, because there is lock on whole table. We also tried to use only one connection across all methods, but same behavior. Our DBMS is SQL Server 2008. Is there something what we didn't understood?
Regards Anton Kalcik
See this sample code:
class Program
{
public class DAL
{
private const string _connectionString = @"Data Source=localhost\fsdf;Initial Catalog=fasdfsa;Integrated Security=SSPI;";
private const string inserttStr = @"INSERT INTO dbo.testTable (test) VALUES(@test);";
/// <summary>
/// Execute command on DBMS.
/// </summary>
/// <param name="command">Command to execute.</param>
private void ExecuteNonQuery(IDbCommand command)
{
if (command == null)
throw new ArgumentNullException("Parameter 'command' can't be null!");
using (IDbConnection connection = new SqlConnection(_connectionString))
{
command.Connection = connection;
connection.Open();
command.ExecuteNonQuery();
}
}
public void FirstMethod()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("@test", "Hello1"));
using (TransactionScope sc = new TransactionScope(TransactionScopeOption.Required))
{
ExecuteNonQuery(command);
sc.Complete();
}
}
public void SecondMethod()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("@test", "Hello2"));
using (TransactionScope sc = new TransactionScope(TransactionScopeOption.Required))
{
ExecuteNonQuery(command);
sc.Complete();
}
}
}
static void Main(string[] args)
{
DAL dal = new DAL();
TransactionOptions tso = new TransactionOptions();
tso.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
using (TransactionScope sc = new TransactionScope(TransactionScopeOption.Required,tso))
{
dal.FirstMethod();
dal.SecondMethod();
sc.Complete();
}
}
}
What is an “Isolation Level”? Database isolation refers to the ability of a database to allow a transaction to execute as if there are no other concurrently running transactions (even though in reality there can be a large number of concurrently running transactions).
The default isolation level is REPEATABLE READ . Other permitted values are READ COMMITTED , READ UNCOMMITTED , and SERIALIZABLE .
The TransactionScope class provides a simple way to mark a block of code as participating in a transaction, without requiring you to interact with the transaction itself. A transaction scope can select and manage the ambient transaction automatically.
TRANSACTION_REPEATABLE_READ means that Derby issues locks to prevent only dirty reads and non-repeatable reads, but not phantoms. It does not issue range locks for selects. TRANSACTION_READ_COMMITTED.
I don't think your issue has anything to do with the .NET TransactionScope concept. Rather, it sounds like you're describing the expected behavior of SQL Server transactions. Also, changing the isolation level only affects "data reads" not "data writes". From SQL Server BOL:
"Choosing a transaction isolation level does not affect the locks acquired to protect data modifications. A transaction always gets an exclusive lock on any data it modifies, and holds that lock until the transaction completes, regardless of the isolation level set for that transaction. For read operations, transaction isolation levels primarily define the level of protection from the effects of modifications made by other transactions."
What that means is that you can prevent the blocking behavior by changing the isolation level for the client issuing the SELECT
statement(s). The READ COMMITED
isolation level (the default) won't prevent blocking. To prevent blocking the client, you would use the READ UNCOMMITTED
isolation level, but you would have to account for the possibility that records may be retrieved that have been updated/inserted by an open transaction (i.e. they might go away if the transaction rolls back).
Good question to talk about transactions.
Your main method is keeping transactions to commit. Even if you commit within other methods, you will still have locks on that row. You will not able to read that table with READ COMMITTED, which is expected, until you commit your locking transaction.
Here is after first method returns:
After second method returns, you will add one more lock to table.
If we execute select statement from a query window with SPID(55), you will see wait status.
After you main method trans commits, you will get the select statement result and it will only show shared lock from our select statement query page.
X means exclusive lock, IX intent locks. You can read more from my blog post about transactions.
If you want to read without wait, you can use nolock hint. If you want to read after first method commits, you can remove that outer scope.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With