事务范围和隔离级别

15

我们在使用TransactionScope时遇到了问题。TransactionScope为我们提供了非常好的灵活性,可以跨越我们的数据访问层使用事务。这样我们可以隐式或显式地使用事务。与ADO.NET事务相比,它还具有一些性能优势,但目前这不是真正的问题。然而,我们在锁定方面存在问题。在下面的示例代码中,虽然隔离级别设置为ReadCommitted,但在主事务(在Main方法中)提交之前,无法从其他客户端对testTable表进行选择SQL语句,因为整个表上有锁。我们还尝试仅在所有方法中使用一个连接,但行为相同。我们的DBMS是SQL Server 2008。我们是否有什么没有理解的地方?

问候 Anton Kalcik

请参阅此示例代码:

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();
        }
    }
}
2个回答

20
我认为你提到的问题与.NET TransactionScope概念无关。相反,它听起来更像是你在描述SQL Server事务的预期行为。此外,更改隔离级别只会影响“数据读取”,而不是“数据写入”。根据SQL Server BOL:
“选择事务隔离级别不会影响获取保护数据修改所需的锁。事务始终会对其修改的任何数据获得独占锁,并保持该锁直到事务完成,而不管为该事务设置的隔离级别如何。对于读操作,事务隔离级别主要定义了保护免受其他事务所做修改影响的水平。”
这意味着您可以通过为发出SELECT语句的客户端更改隔离级别来防止阻塞行为。READ COMMITED隔离级别(默认值)无法防止阻塞。要防止客户端阻塞,您将使用READ UNCOMMITTED隔离级别,但您必须考虑到已由开放事务更新/插入的记录可能被检索(即如果事务回滚,则它们可能消失)。

谢谢您的提示。如果我理解正确的话,TransactionScope 中的事务隔离级别设置只会影响我如何通过对数据库管理系统的读操作来访问数据。 - Anton Kalcik

9

关于事务的问题,这是一个很好的话题。

你的主要方法是保持事务以提交。即使在其他方法中提交,你仍然会锁定该行。在你提交锁定事务之前,你将无法使用READ COMMITTED读取该表,这是预期的。

以下是第一个方法返回后:

First method returns

第二个方法返回后,你将向表中添加一个锁。

secont method returns

如果我们使用SPID(55)的查询窗口执行select语句,你将看到等待状态。

select is waiting

当你的主方法提交事务后,你将获得select语句结果,并且它只会在我们的select语句查询页面上显示共享锁。

Scope commits and select returns

X表示排他锁,IX表示意向锁。 你可以从我的博客文章中了解更多关于事务的内容

如果你想无需等待就进行读取,可以使用nolock提示。如果你想在第一个方法提交后进行读取,可以删除外部范围。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接