sql-server - 存储过程的优化
问题描述
我不是 SQL Server 专家,我有一个表 MainRBDBalance 和另一个 RBDTransaction:
CREATE TABLE [hybarmoney].[MAINRBDBALANCE]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[USERID] [bigint] NULL,
[RBD] [decimal](18, 8) NULL,
[CurrentDollar] [decimal](18, 8) NULL,
[EquivalentRBD] [decimal](18, 8) NULL,
[UpdatedRBD] [decimal](18, 8) NULL,
[PreviousRBDBeforUpdate] [decimal](18, 8) NULL,
[UPDATERBDFROMONEYEAAR] [decimal](18, 8) NULL,
[RBDbeforeupdatefromoneyear] [decimal](18, 8) NULL,
[TOBEADDEDFROM15DAYS] [decimal](18, 8) NULL,
[RBDBEFOREUPDATFROM15DAYS] [decimal](18, 8) NULL
)
RbdTransaction 将具有生成的 otps 以及要从和向谁转移的金额。
CREATE TABLE RbdTransaction
(
ID int identity(1,1),
RBD Decimal (18,8),
OTP Nvarchar(100),
FromUserID bigint,
ToUserID bigint,
Active tinyint,
CreatedDateTime Date
)
对于 RBD 从一个表到另一个表的事务,我写了一个存储过程,如下图。当我提交必要的参数时,存储过程会检查 RBD 是否在 MainRBDBALANCE 表的 FromUsersID 帐户中可用,然后需要检查对于相同的 RbdTransaction 表,则需要更新这两个表。
CREATE PROCEDURE UpdateRBDTransactionMainRBDBalance (
@OTP NVARCHAR(100)
,@FromUserID BIGINT
,@ToUserID BIGINT
,@RBD DECIMAL(18, 8)
)
AS
BEGIN
IF EXISTS (
SELECT TOP 1 1
FROM RbdTransaction
WHERE OTP = @OTP
AND FromUserID = @FromUserID
AND ToUserID = @ToUserID
AND RBD = @RBD
)
BEGIN
IF EXISTS (
SELECT TOP 1 1
FROM hybarmoney.MAINRBDBALANCE
WHERE USERID = @FromUserID
AND RBD >= @RBD
)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @FromUserID
UPDATE RbdTransaction
SET Active = 0
WHERE OTP = @OTP
AND FromUserID = @FromUserID
AND ToUserID = @ToUserID
AND RBD = @RBD
IF EXISTS (
SELECT TOP 1 1
FROM hybarmoney.MAINRBDBALANCE
WHERE USERID = @ToUserID
)
BEGIN
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @ToUserID
END
ELSE
BEGIN
INSERT INTO hybarmoney.MAINRBDBALANCE (
RBD
,USERID
)
VALUES (
@RBD
,@ToUserID
)
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
END
END
END
我需要使用事务,因为我需要同时更新所有表。这是出于我的目的在 sql server 中进行事务的正确方法吗?
解决方案
您对交易的使用很好,但是我倾向于更改您在此处查看余额的方式:
IF EXISTS (
SELECT TOP 1 1
FROM hybarmoney.MAINRBDBALANCE
WHERE USERID = @FromUserID
AND RBD >= @RBD
)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @FromUserID
这为余额在检查和更新之间的变化留下了一个小窗口。相反,我会在更新的同时进行检查:
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @FromUserID
AND RBD >= @RBD;
然后,您可以使用@@ROWCOUNT
检查这是否更新了任何行,如果更新了,则用户有可用资金,如果@@ROWCOUNT
返回 0,则相当于EXISTS
检查失败。
您还可以删除EXISTS
收件人帐户的支票,并将其替换为MERGE
:
MERGE hybarmoney.MAINRBDBALANCE AS t WITH (UPDLOCK, SERIALIZABLE)
USING (VALUES (@ToUserID)) AS s (UserID)
ON t.userID = s.UserID
WHEN MATCHED THEN
UPDATE SET RBD = RBD + @RBD
WHEN NOT MATCHED THEN
INSERT (UserID, USERID) VALUES (s.UserID, @RBD);
这再次消除了竞争条件导致重复用户的机会。此处解释了表锁防止竞争条件的必要性 - “UPSERT” Race Condition With MERGE
所以最后的程序最终会是这样的:
CREATE PROCEDURE UpdateRBDTransactionMainRBDBalance (
@OTP NVARCHAR(100)
,@FromUserID BIGINT
,@ToUserID BIGINT
,@RBD DECIMAL(18, 8)
)
AS
BEGIN
IF EXISTS (
SELECT 1
FROM RbdTransaction
WHERE OTP = @OTP
AND FromUserID = @FromUserID
AND ToUserID = @ToUserID
AND RBD = @RBD
)
BEGIN
BEGIN TRY
BEGIN TRANSACTION
UPDATE hybarmoney.MAINRBDBALANCE
SET RBD = RBD - @RBD
WHERE USERID = @FromUserID
AND RBD >= @RBD;
IF @@ROWCOUNT > 0
BEGIN
UPDATE RbdTransaction
SET Active = 0
WHERE OTP = @OTP
AND FromUserID = @FromUserID
AND ToUserID = @ToUserID
AND RBD = @RBD;
MERGE hybarmoney.MAINRBDBALANCE AS t WITH (UPDLOCK, SERIALIZABLE)
USING (VALUES (@ToUserID)) AS s (UserID)
ON t.userID = s.UserID
WHEN MATCHED THEN
UPDATE SET RBD = RBD + @RBD
WHEN NOT MATCHED THEN
INSERT (UserID, USERID) VALUES (s.UserID, @RBD);
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
END
END
推荐阅读
- python - 无法更改 Python Mathplot 的标签大小
- sql-server - 填充到表列时,如何将日期字段文件中的无效条目转换为 NULL?
- javascript - 用柏树找不到 iframe
- logstash - 如何为 Logstash 输入读取 gzipped json?
- java - 使用 2 个 for 循环创建具有给定字符串的三角形
- matlab - 用于 nvidia tesla k40m gpu 的 ubuntu 18.04 驱动程序设置,用于与 matlab 一起使用
- c# - 方法 'System.String TrimStart(Char[])' 仅在 LINQ to Entities 中受支持
- python - 如何使用 BeautifulSoup 进行解析
标签好像它们被关闭了?
- django - Django 2:如何在管理界面中强制使用外键
- javascript - 如何在羽毛 js 的响应中返回非 json 数据?