我正在尝试开发库存管理应用程序,目前我正在尝试对通过交易添加/删除项目进行概念验证。 该站点将托管在 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);
});
}
}
异常(exception)情况:
您能帮我找出问题所在吗?
最佳答案
问题有两个方面。
首先,没有办法等待死锁。用 SQL Server 术语来说,可以等待 block
结束。但死锁
意味着多个查询正在等待其他查询释放对同一个数据*的独占保留,同时它们自己也持有其他数据上的锁,而其他查询也都在等待。如果所有人继续等待其他人放弃和释放,则什么也不会发生,并且他们将永远被锁定。 SQL Sever 检测到这一情况并强制终止并回滚除一个查询之外的所有查询。
这导致了第二个问题。
您正在强制执行可序列化
隔离级别事务,这是最严格的。在您的代码中,这种隔离级别的需求是可以理解的,因为您更新计数器并需要读取/递增/写入操作是原子的。但是,当对同一行数据的读写查询并行发生在两个不同的线程中时,就会出现问题。
这一切都导致了一个相当简单的解决方案。有一种比您正在使用的方法更好的方法来更新数量。可以在一条语句中完成所有操作,而不是离散读取、增量和写入 Sql 查询。
command.CommandText = "UPDATE StockItems " +
"SET [Quantity] = [Quantity] + " + argQuantity +
" WHERE [Id] = '" + argProductId + "'";
command.ExecuteNonQuery();
transaction.Commit();
现在,由于这是单个语句,因此您不再需要事务。所有这些代码都可以消失。
* 数据不是很精确(故意)。事实上,有许多不同类型的 SQL Server 锁处于不同的粒度级别,它们一起工作以最大限度地减少对相同数据的争用并最大限度地提高性能,但这个细节对于答案来说并不重要。
关于c# - 如何等待 SqlTransaction 提交而不是抛出 SqlException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25405040/