我想知道我是否遵循正确的方法并需要你的帮助来弄明白
这是我的非受保护查询
DECLARE @cl_WordId bigint = NULL
SELECT
@cl_WordId = cl_WordId
FROM tblWords
WHERE cl_Word = @cl_Word
AND cl_WordLangCode = @cl_WordLangCode
IF (@cl_WordId IS NULL)
BEGIN
INSERT INTO tblWords (cl_Word, cl_WordLangCode, cl_SourceId)
VALUES (@cl_Word, @cl_WordLangCode, @cl_SourceId)
SET @cl_WordId = SCOPE_IDENTITY()
SELECT
@cl_WordId
END
ELSE
BEGIN
SELECT
@cl_WordId
END
为了保护它,我将其修改如下
DECLARE @cl_WordId bigint = NULL
SELECT
@cl_WordId = cl_WordId
FROM tblWords WITH (HOLDLOCK)
WHERE cl_Word = @cl_Word
AND cl_WordLangCode = @cl_WordLangCode
BEGIN
IF (@cl_WordId IS NULL)
BEGIN
INSERT INTO tblWords (cl_Word, cl_WordLangCode, cl_SourceId)
VALUES (@cl_Word, @cl_WordLangCode, @cl_SourceId)
SET @cl_WordId = SCOPE_IDENTITY()
SELECT
@cl_WordId
END
ELSE
BEGIN
SELECT
@cl_WordId
END
END
所以我已将WITH (HOLDLOCK)
添加到选择查询中,并将begin
和end
添加到选择查询
此方法是否正确以防止条件INSERT / UPDATE竞争条件
答案 0 :(得分:1)
我在上一个问题(Conditional INSERT/UPDATE Race Condition和“UPSERT” Race Condition With MERGE)中使用MERGE
和HOLDLOCK
发布的文章中提到的是线程安全的,因此您的查询将是:
MERGE tblWords WITH (HOLDLOCK) AS w
USING (VALUES (@cl_Word, @cl_WordLangCode, @cl_SourceId)) AS s (cl_Word, cl_WordLangCode, cl_SourceId)
ON s.cl_Word = w.cl_Word
AND s.cl_WordLangCode = w.cl_WordLangCode
WHEN NOT MATCHED THEN
INSERT (cl_Word, cl_WordLangCode, cl_SourceId)
VALUES (s.cl_Word, s.cl_WordLangCode, s.cl_SourceId);
看起来这可能是一个存储过程,并且您正在使用SELECT @cl_WordId
将ID返回给调用者。这属于Aaron Bertrand's bad habits to kick之一,而应使用输出参数,例如:
CREATE PROCEDURE dbo.SaveCLWord
@cl_Word VARCHAR(255),
@cl_WordLangCode VARCHAR(255),
@cl_SourceId INT,
@cl_WordId INT OUTPUT
AS
BEGIN
MERGE tblWords WITH (HOLDLOCK) AS w
USING (VALUES (@cl_Word, @cl_WordLangCode, @cl_SourceId)) AS s (cl_Word, cl_WordLangCode, cl_SourceId)
ON s.cl_Word = w.cl_Word
AND s.cl_WordLangCode = w.cl_WordLangCode
WHEN NOT MATCHED THEN
INSERT (cl_Word, cl_WordLangCode, cl_SourceId)
VALUES (s.cl_Word, s.cl_WordLangCode, s.cl_SourceId);
SELECT @cl_WordId = w.cl_WordId
FROM tblWords AS w
WHERE s.cl_Word = @cl_Word
AND s.cl_WordLangCode = @cl_WordLangCode;
END
<强> ADDEDNUM 强>
您可以在没有MERGE
的情况下执行此操作,如下所示。
BEGIN TRAN
INSERT tblWords (cl_Word, cl_WordLangCode, cl_SourceId)
SELECT @cl_Word, @cl_WordLangCode, @cl_SourceId
WHERE NOT EXISTS
( SELECT 1
FROM tblWords WITH (UPDLOCK, HOLDLOCK)
WHERE cl_Word = @cl_Word
AND l_WordLangCode = @cl_WordLangCode
);
COMMIT TRAN;
SELECT @cl_WordId = w.cl_WordId
FROM tblWords AS w
WHERE s.cl_Word = @cl_Word
AND s.cl_WordLangCode = @cl_WordLangCode;
如果你没有使用合并,因为你是concerned about its bugs,或者因为在这种情况下你实际上没有UPDATE
,那么MERGE
是过度杀手而且是{{ 1}}就足够了,那就足够了。但是没有使用它,因为它是不熟悉的语法不是最好的理由,花时间阅读它,了解更多,并在SQL弓中添加另一个字符串。
修改强>
<强> HOLDLOCK 强>
相当于SERIALIZABLE。有关更多信息,请参阅本主题后面的SERIALIZABLE。 HOLDLOCK仅适用于指定了它的表或视图,仅适用于中使用的语句定义的事务的持续时间。 HOLDLOCK不能在包含FOR BROWSE选项的SELECT语句中使用。
因此,在您的查询中,您有6个语句:
INSERT
由于您没有显式事务,因此每个语句都在其自己的隐式事务中运行,因此专注于语句2,这相当于:
-- STATETMENT 1
DECLARE @cl_WordId bigint = NULL
--STATEMENT 2
SELECT
@cl_WordId = cl_WordId
FROM tblWords WITH (HOLDLOCK)
WHERE cl_Word = @cl_Word
AND cl_WordLangCode = @cl_WordLangCode
BEGIN
--STATEMENT 3
IF (@cl_WordId IS NULL)
BEGIN
-- STATEMENT 4
INSERT INTO tblWords (cl_Word, cl_WordLangCode, cl_SourceId)
VALUES (@cl_Word, @cl_WordLangCode, @cl_SourceId)
SET @cl_WordId = SCOPE_IDENTITY()
--STATEMENT 5
SELECT
@cl_WordId
END
ELSE
BEGIN
-- STATEMENT 6
SELECT
@cl_WordId
END
END
因此,由于BEGIN TRAN
SELECT
@cl_WordId = cl_WordId
FROM tblWords WITH (HOLDLOCK)
WHERE cl_Word = @cl_Word
AND cl_WordLangCode = @cl_WordLangCode
COMMIT TRAN
适用于使用它的事务的持续时间,因此锁定被释放,一旦此代码完成就会释放锁定,因此当您进入语句3时另外一个线程可以插入到表中。