×

Loading...
Ad by
  • 推荐 OXIO 加拿大高速网络,最低月费仅$40. 使用推荐码 RCR37MB 可获得一个月的免费服务
Ad by
  • 推荐 OXIO 加拿大高速网络,最低月费仅$40. 使用推荐码 RCR37MB 可获得一个月的免费服务

The table definition & the stored procedure inside, please let me know if there's design problem, I would appreciate any suggestion

- Table -
ID int
AccountNumber int
ReceivedDate smalldatetime
Credit decimal(9,2)
Debit decimal(9,2)
Balance decimal(9,2)

- Stored Procedure -

CREATE PROCEDURE DoTransaction @AccountNumber INT, @Amount DECIMAL(9, 2)
AS

DECLARE @Credit DECIMAL(9, 2)
DECLARE @Debit DECIMAL(9, 2)
DECLARE @Balance DECIMAL(9, 2)

SET @Balance = 0

IF @Amount > 0
BEGIN
SET @Debit = 0
SET @Credit = @Amount
END
ELSE
BEGIN
SET @Debit = ABS(@Amount)
SET @Credit = 0
END

SELECT TOP 1 @Balance=Balance FROM BrokerTransactions WHERE AccountNumber=@AccountNumber ORDER BY ID DESC
INSERT INTO BrokerTransactions VALUES(@AccountNumber, GETDATE(), @Debit, @Credit, @Balance + @Amount)
Report

Replies, comments and Discussions:

  • 工作学习 / 专业技术讨论 / 数据库问题请教, 要添加一条记录到表中,但新的记录是通过取得表中最后一条记录计算而来,如何解决并发的问题,细节如下,谢谢
    declare @result int
    select top 1 @result=amount from TableA order by xxxx
    insert into TableA (amount) values(@result + 100)

    可能两个用户同时使用这个表,执行同样的Stored Procedure, 可能导致计算上的错误

    是否可以用 set transaction isolation level 去保证表被锁住在执行Stored Prodecure的过程中? 谢谢!
    • You can add these code to a transaction
      • Thanks for your reply, I'm not good at the database, can transaction guarantee the operation is atomic? Is it good if the statement is "insert into TableA (amount) values( (select top 1 amount from TableA order by xxxx) + 100)"
        • Transaction has to be A(atomic)CID. Whatever your statement is, how many they are, they'll be executed atomically along as they're in one transaction.
    • order by xxxx 跟 amount 是什么关系?amount 是全表唯一的吗?
      • order by ReceivedDate, the table structure: -ID-ReceivedDate-Amount- Like the bank transaction, you deposit money, then your account balance got updated, the amount is the balance
        • typical design for this case: maintain the balance in accounts table; keep a snapshot of balance in the transaction record.
          So what you do in this procedure is: update the account balance with balance + amount returning the current balance into variable; insert into transactions table with amount and current balance. Concurrent transactions will wait for the lock on accounts table (but the lock is on one row only). If SQL SERVER supports row level lock, this is much better than setting the isolation to SERIALIZABLE.
          • Do you mean I can use the TABLOCKX when I do the search, then release the lock after transaction? Thank you.
            • you don't need to use locks explicitly. Update will create a lock for you until you commit or rollback the transaction
    • 既然数据能算出来,那么他本身就是冗余数据,把这个列删了你的问题就解决了。
      • I can not delete the history which is needed for later report, below is the scenario. Thanks 2007/07/01 200$ (Deposit) 2007/07/02 200$ + 100$ = 300$ (Deposit) 2007/07/04 300$ - 50$=250$ (Withdraw) 2007/07/05 250$ + 400$ = 650$ (Deposit)
        • 明白了。第一个checkin的时候先lock表,完事后release,如果并发,即发现表锁了,就等头一个完事重新来。看看CVS或VSS的机制
          • By using TABLOCKX right? thanks. I never used TABLOCKX before, hopefully that won't have dead lock in the procdure
            • you will suffer from low performance if you lock the whole table while only row level lock is needed.
        • So you have one amount column or two? In this row: 2007/07/04 300$ - 50$=250$ do you store -50 in another column? If you are getting balance in this way then there's a problem with your design
          • Yes, the full table structure is -ID-ReceivedDate-Credit-Debit-Balance
            • what's the indexes on this table? How many records in this table?
              • Index on AccountNumber ReceivedDate. ID is the primary key. Thanks.
                • I read your statement below, for a small table, it is ok, if this table has millions of data, you will have deadlock issue, nobody can have other operations when this one is running. If it is taking too long a time, clients will complain.
                  • This table could have millions of records for sure, I don't want to use any lock if possible. But don't know, the table itself is pretty simple, just it will be used by lots of users at the same time.
                    • In that case, I am afraid you'd better following the newkid's suggestion as #3787229@0
                      • What about the last try I posted, use one SQL sentence, I'm not sure how the SQL server handles "insert xxx select from'...
                        • no difference.
          • Any suggestion is appreciated!
          • The table definition & the stored procedure inside, please let me know if there's design problem, I would appreciate any suggestion
            - Table -
            ID int
            AccountNumber int
            ReceivedDate smalldatetime
            Credit decimal(9,2)
            Debit decimal(9,2)
            Balance decimal(9,2)

            - Stored Procedure -

            CREATE PROCEDURE DoTransaction @AccountNumber INT, @Amount DECIMAL(9, 2)
            AS

            DECLARE @Credit DECIMAL(9, 2)
            DECLARE @Debit DECIMAL(9, 2)
            DECLARE @Balance DECIMAL(9, 2)

            SET @Balance = 0

            IF @Amount > 0
            BEGIN
            SET @Debit = 0
            SET @Credit = @Amount
            END
            ELSE
            BEGIN
            SET @Debit = ABS(@Amount)
            SET @Credit = 0
            END

            SELECT TOP 1 @Balance=Balance FROM BrokerTransactions WHERE AccountNumber=@AccountNumber ORDER BY ID DESC
            INSERT INTO BrokerTransactions VALUES(@AccountNumber, GETDATE(), @Debit, @Credit, @Balance + @Amount)
            • my suggestion is to keep the current balance in another table: accounts. So you get balance from that table by account id, not from the transactions table.
              This will save you from the overhead of select...top 1...
              By updating the balance first, you get your transaction serialized for the same account id (concurrent transactions by other account ids are still parallelized) and no need to worry about locks because database will hold the lock for you.
              • Thanks but I'm not good at the database, not quite sure how to do it, can you please give me a bit details.
                • you need to create another table: accounts with account id as primary key and current balance and other properties of an account. I believe you already have one, talk to your database guys
                  • Thanks a lot for your help, just last try "INSERT INTO Transactions VALUES(@AccountNumber, GETDATE(), @Debit, @Credit, (SELECT TOP 1 @Balance=Balance FROM Transactions WHERE AccountNumber=xxx ORDER BY ID) + xxx)", If this won't help, I'll ask them.
                    • it probably won't work. You can test in this way:
                      run your procedure but don't commit;
                      open another session, run the procedure with the same account id.
                      commit both transactions and check the data that you insert.

                      in oracle the second select won't be blocked so you get the same balance as the first one. I don't know what will happen in sql server.
                      • Specially thank you newkid, your suggestion solves another issue I have which is archiving the transactions table because the actual balance is stored in another table, I don't have the headache to keep the top1 for all the accounts.
    • thank you guys, I'll probably go with the way newkid suggested. Thanks again have a good weekend.