Tsql 强制执行原子操作x锁

Tsql 强制执行原子操作x锁,tsql,sql-server-2008-r2,atomic,Tsql,Sql Server 2008 R2,Atomic,我有一个类似于银行账户的数据库模型(一个用于操作的表,一个用于更新余额的触发器)。我目前正在使用SQL SERVER 2008 R2 TABLE OPERATIONS ---------------- VL_CREDIT decimal(10,2) VL_DEBIT decimal(10,2) TABLE BALANCE ------------- DT_OPERATION datetime VL_CURRENT decimal(10,2) PROCEDURE INSERT_OPERATI

我有一个类似于银行账户的数据库模型(一个用于操作的表,一个用于更新余额的触发器)。我目前正在使用SQL SERVER 2008 R2

TABLE OPERATIONS
----------------
VL_CREDIT decimal(10,2)
VL_DEBIT decimal(10,2)

TABLE BALANCE
-------------
DT_OPERATION datetime
VL_CURRENT decimal(10,2)


PROCEDURE INSERT_OPERATION
--------------------------
GET LAST BALANCE BY DATE
CHECK IF VALUE OF OPERATION > BALANCE
   IF > RETURN ERROR
   ELSE INSERT INTO OPERATION(...,....)
我的问题如下:

插入操作的过程必须在插入操作之前检查余额,以查看是否有可用资金,因此余额永远不会变为负值。如果没有余额,我会返回一些代码告诉用户余额不够

我关心的是:如果这个过程连续多次被调用,我如何保证它是原子的

我有一些想法,但我不确定哪一个可以保证:

  • 在操作程序上开始交易
  • 在选择平衡表时有某种锁,但它必须保持到过程执行结束
你能提出一些方法来保证这一点吗?提前谢谢

更新
我在MSDN()上读到,如果我的过程有BEGIN/END TRANSACTION,并且SELECT on TABLOCKX表余额有WITH(TABLOCKX),它将锁定表直到事务结束,因此如果在第一个过程执行期间对该过程进行后续调用,它将等待,然后保证值始终是最后更新的。行吗?如果是这样,这是最佳实践吗?

如果您愿意更改表结构,我会这样构建:

create table Transactions (
    SequenceNo int not null,
    OpeningBalance decimal(38,4) not null,
    Amount decimal(38,4) not null,
    ClosingBalance as CONVERT(decimal(38,4),OpeningBalance + Amount) persisted,
    PrevSequenceNo as CASE WHEN SequenceNo > 1 THEN SequenceNo - 1 END persisted,
    constraint CK_Transaction_Sequence CHECK (SequenceNo > 0),
    constraint PK_Transaction_Sequence PRIMARY KEY (SequenceNo),
    constraint CK_Transaction_NotNegative CHECK (OpeningBalance + Amount >= 0),
    constraint UQ_Transaction_BalanceCheck UNIQUE (SequenceNo, ClosingBalance),
    constraint FK_Transaction_BalanceCheck FOREIGN KEY
                    (PrevSequenceNo, OpeningBalance)
                references Transactions
                    (SequenceNo,ClosingBalance)
    /* Optional - another check that Transaction 1 has 0 amount and
       0 opening balance, if required */
)
您只需将贷项和借项作为
金额的+ve或-ve值应用。上述结构足以强制执行“不转负”要求(通过
CK\u Transaction\u NotNegative
),并确保您了解当前余额(通过查找具有最高
SequenceNo
的行,并获取
ClosingBalance
值。将
UQ\U事务\u余额检查
FK\u事务\u余额检查
(以及计算列)结合在一起)确保整个交易序列有效,并且
PK\u Transaction\u sequence
保持一切有序

因此,如果我们用一些数据填充它:

insert into Transactions (SequenceNo,OpeningBalance,Amount) values
(1,0.0,10.0),
(2,10.0,-5.50),
(3,4.50,2.75)
现在我们可以尝试插入(这可能是
insert\u过程
,其中
@NewAmount
作为参数传递):


此插入失败,因为它会导致余额变为负值。但是如果
@NewAmount
足够小,则插入会成功。如果“同时”尝试两次插入,则a)它们之间的距离刚好足够远,因此它们都成功,并且余额都保持正确,或者b)其中一个将收到PK违规错误。

不幸的是,我无法更改结构,我当时只能创建/更改过程,这就是为什么我要寻找某种类型的锁。但是谢谢你迄今为止的帮助。如果你能帮我,我将不胜感激@蒂齐亚尼-我现在没有什么好建议。单独存储/取消选中余额的问题在于,尽管您尽了最大努力,但它迟早会与交易不同步。我非常喜欢您的解决方案,我会努力获得更改模型的权限。但是你做的方式,当我尝试做一个将closingbalance变成负数的插入时,会产生一个错误,对吗?我希望至少能够在一个过程中检查它,如果是这样的话,我会阻止插入,并返回一个错误,说“balance negative”。谢谢你的解决方案!
declare @NewAmount decimal(38,4)
set @NewAmount = -15.50

;With LastTransaction as (
    select SequenceNo,ClosingBalance,
           ROW_NUMBER() OVER (ORDER BY SequenceNo desc) as rn
    from Transactions
)
insert into Transactions (SequenceNo,OpeningBalance,Amount)
select SequenceNo + 1, ClosingBalance, @NewAmount
from LastTransaction
where rn = 1