执行原子操作x锁定

时间:2014-01-17 11:48:03

标签: 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_OPERATION
--------------------------
GET LAST BALANCE BY DATE
CHECK IF VALUE OF OPERATION > BALANCE
   IF > RETURN ERROR
   ELSE INSERT INTO OPERATION(...,....)

我遇到的问题如下:

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

我担心的是:如果连续多次调用此过程,我该如何保证它是原子的?

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

    关于“操作程序”的
  • BEGIN TRANSACTION
  • 选择BALANCE表时会有某种锁定,但它必须保持到程序执行结束

你能建议一些方法来保证吗?提前谢谢。

更新

我在MSDN(http://technet.microsoft.com/en-us/library/ms187373.aspx)上读到,如果我的程序有BEGIN / END TRANSACTION,并且表BALANCE上的SELECT有WITH(TABLOCKX),它会锁定表直到事务结束,所以如果后续调用此过程是在执行第一个过程中进行的,它将等待,然后保证该值始终是最后一次更新。它会起作用吗?如果是这样,这是最好的做法吗?

1 个答案:

答案 0 :(得分:1)

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

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 */
)

您只需将信用和借记用作Amount的+ ve或-ve值。上述结构足以强制执行"不会消极的"要求(通过CK_Transaction_NotNegative),它还可以确保您了解当前余额(通过查找具有最高SequenceNo行并取ClosingBalance值的行。UQ_Transaction_BalanceCheckFK_Transaction_BalanceCheck(和计算列)确保整个事务序列有效,PK_Transaction_Sequence保持所有内容按顺序构建

所以,如果我们用一些数据填充它:

insert into Transactions (SequenceNo,OpeningBalance,Amount) values
(1,0.0,10.0),
(2,10.0,-5.50),
(3,4.50,2.75)

现在我们可以尝试插入(这可能是INSERT_PROCEDURE@NewAmount作为参数传递):

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

此插入失败,因为它会导致余额变为负数。但如果@NewAmount足够小,它就会成功。如果在"同时尝试两次插入"那么要么a)他们实际上相距甚远,他们都成功了,余额都保持正确,或者b)其中一个会收到PK违规错误。