SQL Server行锁定的正确实现——类ERP WPF应用场景
处理ERP订单场景的SQL Server行锁定方案(WPF应用)
嘿,针对你在WPF类ERP应用中需要锁定订单记录直到用户操作完成的需求,我结合SQL Server的特性和WPF的实际场景,给你梳理几个实用的方案和注意事项:
一、核心思路:用事务+锁定提示实现悲观锁
你的思路完全正确,事务是实现行锁定的基础,但要搭配合适的表提示,才能确保选中的记录在操作完成前不被其他用户修改或读取(根据你的需求)。
1. 关键锁定提示选择
SQL Server提供了几种表提示来控制行锁定行为,针对你的场景推荐两种:
UPDLOCK, HOLDLOCK:获取更新锁并保持到事务结束,其他用户可以读取该记录(但无法加更新锁或排他锁,也就无法修改)。适合允许其他用户查看但不允许修改的场景。XLOCK, HOLDLOCK:获取排他锁并保持到事务结束,其他用户既不能修改也不能读取该记录(默认READ COMMITTED隔离级别下)。适合需要完全锁定、禁止其他用户访问的场景。
2. WPF端的实现步骤
和你之前的短连接模式不同,事务期间需要保持数据库连接打开,直到用户完成操作(保存/取消):
- 打开连接并启动事务
- 查询时带上锁定提示,获取目标记录并绑定到WPF UI
- 等待用户完成编辑操作
- 根据用户选择提交事务(保存修改)或回滚事务(取消操作)
- 最后关闭连接
3. 代码示例(C# + WPF)
// 假设selectedOrderId是用户选中的订单ID int selectedOrderId = 123; using (var connection = new SqlConnection("你的SQL Server连接字符串")) { connection.Open(); // 启动事务,默认隔离级别READ COMMITTED足够满足需求 using (var transaction = connection.BeginTransaction()) { try { // 查询并锁定订单记录,这里用UPDLOCK,HOLDLOCK允许其他用户读取但不能修改 var queryCmd = new SqlCommand( "SELECT OrderID, CustomerName, OrderStatus, TotalAmount FROM Orders WHERE OrderID = @OrderID WITH (UPDLOCK, HOLDLOCK)", connection, transaction); queryCmd.Parameters.AddWithValue("@OrderID", selectedOrderId); using (var reader = queryCmd.ExecuteReader()) { if (!reader.Read()) { MessageBox.Show("未找到指定订单!"); transaction.Rollback(); return; } // 将数据映射到实体类,绑定到WPF编辑窗口 var order = new Order { OrderID = (int)reader["OrderID"], CustomerName = reader["CustomerName"].ToString(), OrderStatus = reader["OrderStatus"].ToString(), TotalAmount = (decimal)reader["TotalAmount"] }; } // 打开WPF编辑窗口,等待用户操作 var editWindow = new OrderEditWindow(order); if (editWindow.ShowDialog() == true) { // 用户点击保存,执行更新操作 var updateCmd = new SqlCommand( "UPDATE Orders SET CustomerName = @CustomerName, OrderStatus = @OrderStatus, TotalAmount = @TotalAmount WHERE OrderID = @OrderID", connection, transaction); updateCmd.Parameters.AddWithValue("@CustomerName", order.CustomerName); updateCmd.Parameters.AddWithValue("@OrderStatus", order.OrderStatus); updateCmd.Parameters.AddWithValue("@TotalAmount", order.TotalAmount); updateCmd.Parameters.AddWithValue("@OrderID", order.OrderID); updateCmd.ExecuteNonQuery(); transaction.Commit(); MessageBox.Show("订单保存成功!"); } else { // 用户取消操作,回滚事务释放锁 transaction.Rollback(); } } catch (Exception ex) { // 发生异常时必须回滚事务,避免长期锁定 transaction.Rollback(); MessageBox.Show($"操作失败:{ex.Message}"); } finally { connection.Close(); } } }
二、备选方案:乐观锁(适合并发高但冲突少的场景)
如果你的ERP系统并发量很高,不想长时间占用锁资源,可以考虑乐观锁方案:
- 在
Orders表中添加一个RowVersion(时间戳)字段,SQL Server会自动维护这个字段的值 - 查询时获取该字段的值,编辑完成后更新时,带上这个版本号做判断:
UPDATE Orders SET CustomerName = @CustomerName, OrderStatus = @OrderStatus WHERE OrderID = @OrderID AND RowVersion = @OriginalRowVersion - 如果更新影响行数为0,说明该记录已经被其他用户修改,此时提示用户重新获取最新数据
这种方式不会锁定行,只是在更新时检查冲突,适合不需要完全阻止读取、只需要避免覆盖修改的场景。
三、重要注意事项
- 缩短事务时间:尽量避免让事务长时间处于打开状态(比如用户打开编辑窗口后长时间不操作),可以添加超时机制(比如15分钟无操作自动回滚事务)。
- 死锁预防:如果涉及多表或多行锁定,确保所有用户按照相同的顺序锁定资源(比如按OrderID从小到大锁定),避免死锁。
- 连接管理:事务期间必须保持连接打开,但要确保在任何异常情况下都能关闭连接,避免连接泄漏。
- 性能考量:排他锁(XLOCK)会严重影响并发性能,除非必要,优先使用更新锁(UPDLOCK)。
内容的提问来源于stack exchange,提问作者josef_skywalker
相关产品推荐
相关产品推荐

