如何在同时调用多次的存储过程中读取更新的数据
How to read updated data in a stored procedure called multiple times simultaneously
有2个table:
- 钱包;
- 交易。
有一个存储过程处理(我想用 ACID 操作):
- 正在更新钱包 table
- 正在向事务中插入一行 table
每次调用。
当有多个同时调用SP时出现问题,实际上PreviousBalance的值不正确(顺序错误),导致SP中读取旧值,同时另一个调用过程是运行.
要更好地理解,请查看以下屏幕截图。
有 3 个具有相同 DT 的交易(ID 1289、1288、1287),在所有这些交易中,PreviouseBalance 是相等的,但不正确,因为 :
的值
- Trx ID 1288 应为 180,78 作为上一行的余额;
- Trx ID 1289 应该是 168,07 = 180,78 - 12,08
我认为问题出在@OLDBalance var 的 SET 中;同时这 3 个线程读取相同的值,因此当 SP 转到 INSERT 时加载相同的 PreviousBalance 值。
我该怎么做才能在提交一个操作后正确读取@OLDBalance?
我尝试在SP中设置了几种类型的Isolation Levet,结果都是一样的,有时会出错死锁。
我有以下存储过程:
存储过程
ALTER PROCEDURE [dbo].[upsMovimenta_internal]
@AccountID int,
@Amount money,
@TypeTransactionID int,
@ProductID int,
@notes nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @OLDBalance MONEY;
DECLARE @PreviousBalance MONEY;
DECLARE @CurrentBalance MONEY;
DECLARE @Molt float;
BEGIN TRANSACTION;
IF NOT EXISTS( SELECT * FROM Accounts WHERE AccountID = @AccountID)
BEGIN
RaisError(N'Account not found ', 10, 1, N'number', 3);
return (1)
END
SELECT @Molt = Moltiplicatore
FROM TypeTransactions
where TypeTransactionID = @TypeTransactionID
;
IF (@Molt is null )
BEGIN
RaisError(N'Error transaction', 10, 1, N'number', 3);
return (1)
END
SET @Amount = @Amount * @Molt;
--SELECT * FROM Wallets
SELECT TOP 1 @OLDBalance = TotalAmount
FROM Wallets
where AccountID = @AccountID
;
SET @CurrentBalance = @OLDBalance + @Amount;
IF (@ProductID = 1 )
BEGIN
UPDATE Wallets
SET TotalAmount+=@Amount,
Cash+=@Amount
FROM Wallets where AccountID = @AccountID
;
END
IF (@ProductID = 2 )
BEGIN
UPDATE Wallets
SET TotalAmount+=@Amount,
Fun+=@Amount
FROM Wallets where AccountID = @AccountID
;
END
INSERT INTO Transactions
( AccountID, ProductID, DT, TypeTransactionID, Amout, Balance, PreviousBalance, Notes )
VALUES
( @AccountID, @ProductID, GETDATE(), @TypeTransactionID, @Amount, @CurrentBalance, @OLDBalance, @notes)
;
COMMIT TRANSACTION;
return (0)
END
非常感谢你们
通常,管理记录锁的一种方法是在开始事务后立即对要处理的行应用虚拟更新。
在这种情况下SQL 服务器保证这些行将被锁定并且没有其他事务可以访问这些行。所以你可以把你的设计改成这样:
begin tran
update myTable
set Field1 = Field1
where someKeyField = 212
-- do the same for other tables that you want to protect against change
-- at this moment all working rows will be locked, other procedure calls will be on hold
-- do your main operations here
commit tran
问题是其他进程调用将等待,这可能会降低性能,甚至 time-out 如果流量很高并且您在此进程中的操作很长
如果您在高事务环境中工作,则需要更改您的设计。
更新:设计建议
我不明白为什么你的交易中有 PreviousBalance
和 Balance
(这违反了设计规则,但是在特殊情况下你可以覆盖规则)。
您可能需要它来加快计算速度或简化查询。但这在 OLTP 数据库中不是好的做法。
规则说您保留 Amount
列并在其他地方计算 PreviousBalance
和 Balance
。
您应该删除 PreviousBalance
但保留 Balance
列,并且每次插入交易时,您都会更新 (increase/decrease) Balance
列。 此外,您需要在第一次交易时初始化 Balance
列。
这是我能想到的。如果我了解你的整个系统,我将能够有更好的想法。
有2个table:
- 钱包;
- 交易。
有一个存储过程处理(我想用 ACID 操作):
- 正在更新钱包 table
- 正在向事务中插入一行 table 每次调用。
当有多个同时调用SP时出现问题,实际上PreviousBalance的值不正确(顺序错误),导致SP中读取旧值,同时另一个调用过程是运行.
要更好地理解,请查看以下屏幕截图。
有 3 个具有相同 DT 的交易(ID 1289、1288、1287),在所有这些交易中,PreviouseBalance 是相等的,但不正确,因为 :
的值- Trx ID 1288 应为 180,78 作为上一行的余额;
- Trx ID 1289 应该是 168,07 = 180,78 - 12,08
我认为问题出在@OLDBalance var 的 SET 中;同时这 3 个线程读取相同的值,因此当 SP 转到 INSERT 时加载相同的 PreviousBalance 值。
我该怎么做才能在提交一个操作后正确读取@OLDBalance? 我尝试在SP中设置了几种类型的Isolation Levet,结果都是一样的,有时会出错死锁。
我有以下存储过程:
存储过程
ALTER PROCEDURE [dbo].[upsMovimenta_internal]
@AccountID int,
@Amount money,
@TypeTransactionID int,
@ProductID int,
@notes nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;
DECLARE @OLDBalance MONEY;
DECLARE @PreviousBalance MONEY;
DECLARE @CurrentBalance MONEY;
DECLARE @Molt float;
BEGIN TRANSACTION;
IF NOT EXISTS( SELECT * FROM Accounts WHERE AccountID = @AccountID)
BEGIN
RaisError(N'Account not found ', 10, 1, N'number', 3);
return (1)
END
SELECT @Molt = Moltiplicatore
FROM TypeTransactions
where TypeTransactionID = @TypeTransactionID
;
IF (@Molt is null )
BEGIN
RaisError(N'Error transaction', 10, 1, N'number', 3);
return (1)
END
SET @Amount = @Amount * @Molt;
--SELECT * FROM Wallets
SELECT TOP 1 @OLDBalance = TotalAmount
FROM Wallets
where AccountID = @AccountID
;
SET @CurrentBalance = @OLDBalance + @Amount;
IF (@ProductID = 1 )
BEGIN
UPDATE Wallets
SET TotalAmount+=@Amount,
Cash+=@Amount
FROM Wallets where AccountID = @AccountID
;
END
IF (@ProductID = 2 )
BEGIN
UPDATE Wallets
SET TotalAmount+=@Amount,
Fun+=@Amount
FROM Wallets where AccountID = @AccountID
;
END
INSERT INTO Transactions
( AccountID, ProductID, DT, TypeTransactionID, Amout, Balance, PreviousBalance, Notes )
VALUES
( @AccountID, @ProductID, GETDATE(), @TypeTransactionID, @Amount, @CurrentBalance, @OLDBalance, @notes)
;
COMMIT TRANSACTION;
return (0)
END
非常感谢你们
通常,管理记录锁的一种方法是在开始事务后立即对要处理的行应用虚拟更新。
在这种情况下SQL 服务器保证这些行将被锁定并且没有其他事务可以访问这些行。所以你可以把你的设计改成这样:
begin tran
update myTable
set Field1 = Field1
where someKeyField = 212
-- do the same for other tables that you want to protect against change
-- at this moment all working rows will be locked, other procedure calls will be on hold
-- do your main operations here
commit tran
问题是其他进程调用将等待,这可能会降低性能,甚至 time-out 如果流量很高并且您在此进程中的操作很长
如果您在高事务环境中工作,则需要更改您的设计。
更新:设计建议
我不明白为什么你的交易中有 PreviousBalance
和 Balance
(这违反了设计规则,但是在特殊情况下你可以覆盖规则)。
您可能需要它来加快计算速度或简化查询。但这在 OLTP 数据库中不是好的做法。
规则说您保留 Amount
列并在其他地方计算 PreviousBalance
和 Balance
。
您应该删除 PreviousBalance
但保留 Balance
列,并且每次插入交易时,您都会更新 (increase/decrease) Balance
列。 此外,您需要在第一次交易时初始化 Balance
列。
这是我能想到的。如果我了解你的整个系统,我将能够有更好的想法。