SQL 事务如何保持数据完整性
How SQL transaction preserves data integrity
让我们以一个可以向多个客户销售书籍的销售服务器为例。
例如:
- 书 { id:1, quantity:10 }
- 书 { id:2, quantity:10 }
...等等
- 客户 1 -> 加入书的购物车{id:1,数量:6}
- 客户 2 -> 加入图书购物车{id:1,数量:7}
当客户点击购买按钮销售服务器时,检查这个条件:
if(buy.quantity <= available.quantity) { //available is freshly fetched from db
sell();
} else {
fail();
}
现在考虑这个案例:
client2
检查此购买条件并继续 sell()
方法。
同时client1
也检查了购买条件并完成了sell()
..
所以现在只有 4
..另一方面,client2
已经通过买入条件并且正在执行 sell()
条件的一半,因此在 client2
的 sell()
之后,将会有 -3
。
如何预防?
交易在这种情况下有何帮助?
您需要使用事务隔离级别,在读取库存级别时会获取共享锁,并持有此锁直到更新完成。这会阻止第二个交易 运行,直到第一个交易完成,防止您讨论的场景。
查看以下 Microsoft 文档以进一步解释 SQL Server https://docs.microsoft.com/en-us/sql/connect/jdbc/understanding-isolation-levels
中的各种隔离级别
当交易期间需要用户操作(例如添加到购物车、结帐)时,永远不要使用显式数据库交易。
通过在自动(单语句)事务中执行 T-SQL 中的计算,可以在没有 long-running 显式事务的情况下避免竞争条件。默认情况下,所有语句都在自动事务中执行。为了在并发环境中工作,更新语句可以使用乐观并发检查,将从数据库中检索的原始值与当前值进行比较:
UPDATE dbo.Inventory
SET AvailableQuantity -= @BuyQuantity
WHERE ProductCode = @ProductCode
AND AvailableQuantity = @OriginalAvailableQuantity;
IF @@ROWCOUNT <> 1 RAISERROR(Transaction rejected - someone updated the row',16,1);
一个rowversion
column可以促进乐观并发检查,这在涉及很多列时特别有用:
UPDATE dbo.Inventory
SET AvailableQuantity -= @BuyQuantity
WHERE ProductCode = @ProductCode
AND RowVersionColumn = @OriginalRowVersionColumn;
IF @@ROWCOUNT <> 1 RAISERROR(Transaction rejected - someone updated the row',16,1);
如果没有乐观并发或过度悲观锁,您可以在UPDATE
语句中添加数量不能为负的业务规则。
UPDATE dbo.Inventory
SET AvailableQuantity -= @BuyQuantity
WHERE ProductCode = @ProductCode
AND AvailableQuantity -= @BuyQuantity >= 0;
IF @@ROWCOUNT <> 1 RAISERROR(Transaction rejected - Available quantity may not be negative',16,1);
这个简化的例子只是为了展示一些乐观并发技术。当涉及多个语句时,人们可以而且应该仍然使用显式事务(例如 all-or-none 对多个项目进行结帐)。一个强大的购物车系统需要考虑放弃或过期的会话以及对可用数量的影响。需要根据业务规则加回库存。
让我们以一个可以向多个客户销售书籍的销售服务器为例。
例如:
- 书 { id:1, quantity:10 }
- 书 { id:2, quantity:10 }
...等等
- 客户 1 -> 加入书的购物车{id:1,数量:6}
- 客户 2 -> 加入图书购物车{id:1,数量:7}
当客户点击购买按钮销售服务器时,检查这个条件:
if(buy.quantity <= available.quantity) { //available is freshly fetched from db
sell();
} else {
fail();
}
现在考虑这个案例:
client2
检查此购买条件并继续 sell()
方法。
同时client1
也检查了购买条件并完成了sell()
..
所以现在只有 4
..另一方面,client2
已经通过买入条件并且正在执行 sell()
条件的一半,因此在 client2
的 sell()
之后,将会有 -3
。
如何预防?
交易在这种情况下有何帮助?
您需要使用事务隔离级别,在读取库存级别时会获取共享锁,并持有此锁直到更新完成。这会阻止第二个交易 运行,直到第一个交易完成,防止您讨论的场景。
查看以下 Microsoft 文档以进一步解释 SQL Server https://docs.microsoft.com/en-us/sql/connect/jdbc/understanding-isolation-levels
中的各种隔离级别当交易期间需要用户操作(例如添加到购物车、结帐)时,永远不要使用显式数据库交易。
通过在自动(单语句)事务中执行 T-SQL 中的计算,可以在没有 long-running 显式事务的情况下避免竞争条件。默认情况下,所有语句都在自动事务中执行。为了在并发环境中工作,更新语句可以使用乐观并发检查,将从数据库中检索的原始值与当前值进行比较:
UPDATE dbo.Inventory
SET AvailableQuantity -= @BuyQuantity
WHERE ProductCode = @ProductCode
AND AvailableQuantity = @OriginalAvailableQuantity;
IF @@ROWCOUNT <> 1 RAISERROR(Transaction rejected - someone updated the row',16,1);
一个rowversion
column可以促进乐观并发检查,这在涉及很多列时特别有用:
UPDATE dbo.Inventory
SET AvailableQuantity -= @BuyQuantity
WHERE ProductCode = @ProductCode
AND RowVersionColumn = @OriginalRowVersionColumn;
IF @@ROWCOUNT <> 1 RAISERROR(Transaction rejected - someone updated the row',16,1);
如果没有乐观并发或过度悲观锁,您可以在UPDATE
语句中添加数量不能为负的业务规则。
UPDATE dbo.Inventory
SET AvailableQuantity -= @BuyQuantity
WHERE ProductCode = @ProductCode
AND AvailableQuantity -= @BuyQuantity >= 0;
IF @@ROWCOUNT <> 1 RAISERROR(Transaction rejected - Available quantity may not be negative',16,1);
这个简化的例子只是为了展示一些乐观并发技术。当涉及多个语句时,人们可以而且应该仍然使用显式事务(例如 all-or-none 对多个项目进行结帐)。一个强大的购物车系统需要考虑放弃或过期的会话以及对可用数量的影响。需要根据业务规则加回库存。