【发布时间】:2017-06-24 03:51:19
【问题描述】:
有人在 SQL Server 2008 中编写了以下存储过程。我认为这不是实现他需要实现的目标的最佳方法之一。
如您所见,我们需要在某些情况下更新MyTable,但在其他情况下则不需要。并且只要存在允许用户更新MyTable 的条件,我们不希望多个用户同时更新MyTable。问题是这个过程的编写方式,所有变量都是使用 select 语句设置的,然后Begin Transaction 开始。
但是鉴于此过程的结构,最好执行sp_getapplock 过程以仅允许一个用户进行更新(考虑到其结构,这可能适用于此过程,但我通读了sp_getapplock 可以导致死锁),其他选项是执行可序列化的READ_COMMITED。
但是在可序列化的情况下,这个存储过程是否允许并发用户之一(比如说 2)仍然选择 col1、col2、col3 和 col4 的旧值并填充变量,如 @onGoingSession、@timeDiff等其他人完成更新并释放行(通过可序列化)时使用这些旧值。由于事务完成后轮到第二个用户。而且我确实想阻止其他用户阅读正在修改的行。
我们不应该在声明变量之后立即将开始事务向上移动并在它之后执行可序列化。因此,当其中一位用户(显然已经获得行锁的用户)正在更新锁定的行但尚未提交时,任何并发用户都不应读取旧值并填充变量。
USE [myDatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[MyTableProc]
(@UserId char(20))
AS
BEGIN
DECLARE @error varchar(200)
DECLARE @warning varchar(300)
DECLARE @timeDiff smallint
DECLARE @onGoingSession bit = 1
DECLARE @userName char(20)
DECLARE @counterOfResync tinyint
SET @counterOfResync = (SELECT [Col1] FROM MyTable)
SET @userName = (SELECT [Col2] FROM MyTable)
SET @onGoingSession = (SELECT [Col3] FROM MyTable)
SET @timeDiff = (SELECT DATEDIFF(MINUTE, Col4, CURRENT_TIMESTAMP) from MyTable)
BEGIN TRANSACTION
IF(@onGoingSession = 1)
BEGIN
IF(@timeDiff >= 360)
BEGIN
UPDATE MyTable
SET UserId = @UserId, Col4 = CURRENT_TIMESTAMP
IF(@@ERROR = 0)
BEGIN
SET @warning = RTRIM('An unfinsihed session for ' +
LTRIM(RTRIM(@userName)) + ' is going on for the past ' +
LTRIM(RTRIM(@timeDiff)) + ' minutes but updates from ' + LTRIM(RTRIM(@UserId)) + ' are successful')
COMMIT
RAISERROR(@warning,7, 1)
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
ELSE
BEGIN
SET @error = RTRIM('A session of updates for '+ LTRIM(RTRIM(@userName))+ ' is already in progress concurrent updates are not allowed')
IF(@@ERROR = 0)
BEGIN
COMMIT
RAISERROR(@error, 8, 1)
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
END
ELSE IF(@onGoingSession = 0 AND @counterOfResync = 0)
BEGIN
UPDATE MyTable
SET UserId = @UserId, Col3 = 1, Col4 = CURRENT_TIMESTAMP
IF(@@ERROR =0)
BEGIN
COMMIT
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
ELSE IF(@onGoingSession = 0 AND @counterOfResync > 0 AND @timeDiff >= 5)
BEGIN
UPDATE MyTable
SET Col3 = 1, CountOfResync = 0, UserId = @UserId, Col4 =
CURRENT_TIMESTAMP
IF(@@ERROR = 0)
BEGIN
COMMIT
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
ELSE
BEGIN
SET @error = RTRIM('A server resync session already in progress, updates can''t be made at the same time')
IF(@@ERROR = 0)
BEGIN
COMMIT
RAISERROR(@error, 9, 1)
RETURN
END
ELSE
BEGIN
ROLLBACK
RETURN @@ERROR
END
END
RETURN @@ERROR
END
【问题讨论】:
-
如果你们不要投票给你们宝贵的建议,我将不胜感激。
-
没有缩进的双倍行距代码不会让我们更容易为您提供帮助。也许你可以让它更整洁一点。然后有四个
select语句来获取MyTable中允许的唯一行。 -
@marc_s 非常感谢您编辑了我的代码。谢谢一堆。谁能回答我的问题,将不胜感激。
-
在事务中移动初始查询并使用带有
updlock提示的单个select获取值应该在不阻塞其他读取器的情况下序列化更新。如果您想在事务处于活动状态但未提交时防止读取陈旧值,那么您将需要类似排他锁的东西。 -
是的,这就是我现在计划在事务中移动初始查询,在此之前
Set ISOLATION LEVEL SERIALIZABLE这样我将防止并发更新以及万一 2 个用户获得共享锁并且轮流更新行。然后,当一个更新另一个正在等待轮到它时,一旦第一个用户的事务完成,第二个用户就会点击任何IF语句,因此它将绕过整个事务并返回错误/警告消息到调用系统或可能会更新。
标签: sql sql-server tsql