0

我正在尝试开发库存管理的应用程序,目前我正在尝试通过交易来添加/删除项目的概念证明。该站点将托管在 Windows Azure 上,因此我必须使用 SqlTransation 来执行事务(无 DTC)。

这是我的 POC 库代码:

public static void AddQuantityByProductId(int argProductId, int argQuantity)
{
    UpdateQuantity(argProductId, argQuantity);
}

public static void ReduceQuantityByProductId(int argProductId, int argQuantity)
{
    UpdateQuantity(argProductId, argQuantity * -1);
}
private static void UpdateQuantity(int argProductId, int argQuantity)
{
        string conn = ConfigurationManager.ConnectionStrings["MyConn"].ConnectionString;
        using (SqlConnection connection = new SqlConnection(conn))
        {
            connection.Open();
            using (SqlTransaction transaction = 
                connection.BeginTransaction(IsolationLevel.Serializable))
            {
                SqlCommand command = connection.CreateCommand();
                command.Transaction = transaction;
                command.CommandTimeout = 30;

                command.CommandText = "select [Quantity] from StockItems where [Id] = '" + argProductId + "'";
                int quantity = (int)command.ExecuteScalar();
                int newQuantity = quantity + argQuantity;
                command.CommandText = "UPDATE StockItems SET [Quantity] = " + newQuantity + " WHERE [Id] = '" + argProductId + "'";
                command.ExecuteNonQuery();
                transaction.Commit();
            }
        }
}

我用来测试系统的代码:

static void Main(string[] args)
{
    int productId = 6; //hard coded Id , because the line already exists
    for (int i = 0; i < 10; i++)
    {
        Parallel.For(0, 10, (j) =>
        {
            StockItemDal.AddQuantityByProductId(productId, 10);
            StockItemDal.ReduceQuantityByProductId(productId, 10);

         });
    }
 }

例外: 每次引发的异常

你能帮我找出问题吗?

4

1 回答 1

2

问题有两个方面。

首先,没有办法等待僵局结束。在 SQL Server 行话中,ablock可以等待。但是,这deadlock意味着多个查询正在等待其他人释放对同一数据*的独占持有,同时自己持有其他人都在等待的其他数据的锁。如果所有人都继续等待其他人放弃并释放,那么什么都不会发生,他们将永远保持锁定状态。SQL Sever 检测到这一点并强制终止和回滚除一个之外的所有查询。

这导致了第二个问题。

您正在强制serializable隔离级别的事务,这是最严格的。在您的代码中,对这种隔离级别的需求是可以理解的,因为您更新了一个计数器并且需要读取/递增/写入操作是原子的。但是当对同一行数据的读写查询并行发生在两个不同的线程中时,就会出现问题。

这一切都导致了一个相当简单的解决方案。有比您使用的方法更好的方法来更新数量。可以在一条语句中完成所有这些操作,而不是离散的读取、递增和写入 Sql 查询。

        command.CommandText = "UPDATE StockItems " + 
            "SET [Quantity] = [Quantity] + " + argQuantity + 
            " WHERE [Id] = '" + argProductId + "'";
        command.ExecuteNonQuery();
        transaction.Commit();

现在,由于这是一条语句,您不再需要事务。所有这些代码都可以消失。

* 数据不是很精确(故意)。事实上,有许多不同类型的不同粒度级别的 SQL Server 锁,它们一起工作以最大限度地减少对相同数据的争用并最大限度地提高性能,但这个细节对于答案并不重要。

于 2014-08-20T14:02:12.767 回答