你搞错了。
与其向Checkouts 添加记录,然后测试Videos 中是否有视频,您需要先检查是否有可以查看的副本。
这就像您从任何商店购买东西一样 - 首先您将产品从货架上拿下来,然后才付款。
如果产品不在货架上,则无需您付款。
第一个版本
您至少需要三个步骤才能做到正确:
首先,您检查是否有要签出的副本。
如果没有,您无需执行任何操作,只需返回一条消息,指出没有可结帐的免费副本。
如果有副本,则需要更新Video表(TotalCopies -= 1)
最后 - 您需要将记录插入到checkouts。
这里最重要的是,如果这些步骤中的任何一个失败,它们都会失败 - 例如,如果由于某种原因您未能将行插入到 checkouts,则必须恢复在 @ 上所做的更新987654328@ 表,因为您无法完成该过程。
这是您需要将整个过程包装在事务中的第一个原因。
您需要事务的第二个原因是,如果有要签出的副本,则避免测试与video 表的更新之间的竞争条件。您可以在 Dan Guzman 关于Conditional INSERT/UPDATE Race Condition 的博客文章中了解更多信息。
所以,说了这么多,让我们展示一些代码:
CREATE PROCEDURE VideoCheckout
(
@VideoId int,
@UserId uniqueIdentifier,
@Success bit OUTPUT
)
AS
SET XACT_ABORT ON
SET @Success = 0
BEGIN TRANSACTION
BEGIN TRY
DECLARE @NumberOfCopies int
SET @NumberOfCopies = ISNULL(
(
SELECT TotalCopies
FROM dbo.Videos WITH (UPDLOCK, HOLDLOCK)
WHERE VideoId = @VideoId
)
, 0)
IF @NumberOfCopies > 0
BEGIN
UPDATE dbo.Videos
SET TotalCopies = TotalCopies - 1
WHERE VideoId = @VideoId;
INSERT INTO dbo.Checkouts (VideoId, UserId, CheckoutDate)
VALUES (@VideoId, @UserId, CURRENT_TIMESTAMP)
SET @Success = 1
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION
END CATCH
GO
更新 - 使用@@rowcount 版本:
SQL Server 的 @@Rowcount 全局变量返回受影响的行数(通常。链接中记录了一些例外情况) - 使用它可以将测试部分与更新部分统一起来 - 如果出现 SQL Server 报告更新不会影响任何行。这使您能够编写更简单的 SQL,并且可能会有更好的性能。
CREATE PROCEDURE VideoCheckout
(
@VideoId int,
@UserId uniqueIdentifier,
@Success bit OUTPUT
)
AS
SET XACT_ABORT ON
SET @Success = 0
BEGIN TRANSACTION
BEGIN TRY
UPDATE dbo.Videos
SET TotalCopies = TotalCopies - 1
WHERE VideoId = @VideoId
AND TotalCopies > 0;
IF @@ROWCOUNT > 0
BEGIN
INSERT INTO dbo.Checkouts (VideoId, UserId, CheckoutDate)
VALUES (@VideoId, @UserId, CURRENT_TIMESTAMP)
SET @Success = 1
END
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION
END CATCH
GO