合并插入/更新可以防止PK冲突和死锁?

时间:2018-08-21 14:31:43

标签: sql-server sql-server-2008 stored-procedures merge sql-merge

我现在有一个存储过程可以处理我的一张表中的插入和更新事务。我仍在测试以查看此解决方案是否存在任何潜在问题以及如何改进该过程。此SP接受几个参数,然后检查匹配的ID,并执行插入或更新。我已阅读有关primary key violation error, showing that MERGE is vulnerable to concurrency problems like a multi-statement conditional INSERT/UPDATE的{​​{3}}。似乎他们已经使用WITH (HOLDLOCK)解决了一些问题。我是存储过程和合并世界的新手。我想在这里发表您的意见,如果这对于高交易量的应用程序来说是可靠的代码?我可能有多个用户插入同一张表或同时运行Update语句。在这种情况下,参数嗅探是否还有任何潜在的问题?如果是,我应该考虑使用OPTION (RECOMPILE)还是仅适用于SELECT搜索查询?这是我的SQL代码的示例:

USE [TestDB]
GO
/****** Object:  StoredProcedure [dbo].[SaveMaster]    Script Date: 08/21/2018 10:05:21 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      M, D
-- Create date: 08/21/2018
-- Description: Insert/Update Master table
-- =============================================
ALTER PROCEDURE [dbo].[SaveMaster] 
   @RecordID INT = NULL,
   @Status BIT = NULL,
   @Name VARCHAR(50) = NULL,
   @Code CHAR(2) = NULL,
   @ActionDt DATETIME = NULL,
   @ActionID UNIQUEIDENTIFIER = NULL    
AS
   MERGE dbo.Master WITH (HOLDLOCK) AS Target
   USING (SELECT @RecordID,@Status,@Name,@Code,@ActionDt,@ActionID) 
   AS Source (RecordID,Status,Name,Code,ActionDt,ActionID)
      ON Target.RecID = Source.RecordID
   WHEN MATCHED THEN
      UPDATE
    SET Target.Status = Source.Status,
        Target.Name = Source.Name,
        Target.Code = Source.Code,
        Target.ActionDt = Source.ActionDt,
        Target.ActionID = Source.ActionID
   WHEN NOT MATCHED THEN
    INSERT(
        Status,Name,Code,ActionDt,ActionID
    )VALUES(
        Source.Status,
        Source.Name,
        Source.Code,
        Source.ActionDt,
        Source.ActionID
    );
   RETURN @@ERROR;

以下是有关如何使用服务器端语言(ColdFusion 2016)调用存储过程的示例:

<cftransaction action="begin">
    <cftry>
         <cfstoredproc procedure="SaveMaster" datasource="#dsn#">
              <cfprocparam dbvarname="@RecordID" value="#trim(arguments.frm_recid)#" cfsqltype="cf_sql_integer" null="#!len(trim(arguments.frm_recid))#" />
          <cfprocparam dbvarname="@Status" value="#trim(arguments.frm_status)#" cfsqltype="cf_sql_bit" null="#!len(trim(arguments.frm_status))#" />
          <cfprocparam dbvarname="@Name" value="#trim(arguments.frm_name)#" cfsqltype="cf_sql_varchar" maxlength="50" null="#!len(trim(arguments.frm_name))#" />
          <cfprocparam dbvarname="@Code" value="#trim(frm_code)#" cfsqltype="cf_sql_char" maxlength="2" null="#!len(trim(frm_code))#" />
          <cfprocparam dbvarname="@ActionDt" value="#trim(NOW())#" cfsqltype="cf_sql_datetime" />
              <cfprocparam dbvarname="@ActionID" value="#trim(SESSION.UserID)#" cfsqltype="cf_sql_idstamp" null="#!len(trim(SESSION.UserID))#" />

          <cfprocresult name="MasterResult"/>
     </cfstoredproc>

         <cfset local.fnResults = {status : "200", message : "Record successully saved!", RecID : MasterResult.RecID}>

         <cfcatch type="any">
           <cftransaction action="rollback" />
             <cfset local.fnResults = {status : "400", message : "Error! Please contact your administrator."}>
         </cfcatch>
     </cftry>
</cftransaction>

如您所见,我期望存储过程返回RecID,应该将其返回(我在存储过程中为现有记录传递的相同ID,或者如果不存在,则将生成并返回该ID,如下所示)插入SELECT SCOPE_IDENTITY() AS RecID;或像这样SELECT @RecordID AS RecID更新)。如果有人有任何建议,并且知道从运行合并合并插入/更新的SP返回RecID的最佳方法,请告诉我。

1 个答案:

答案 0 :(得分:1)

  

如果有人有任何建议,并且知道从运行带有合并的插入/更新的SP返回RecID的最佳方法,请告诉我。

您可以在MERGE语句中添加OUTPUT clause。这样一来,您就可以返回包含新ID的结果集,并且可以根据需要选择返回的结果:

   MERGE dbo.Master WITH (HOLDLOCK) AS Target
   USING (SELECT @RecordID,@Status,@Name,@Code,@ActionDt,@ActionID) 
   AS Source (RecordID,Status,Name,Code,ActionDt,ActionID)
      ON Target.RecID = Source.RecordID
   WHEN MATCHED THEN
      UPDATE
    SET Target.Status = Source.Status,
        Target.Name = Source.Name,
        Target.Code = Source.Code,
        Target.ActionDt = Source.ActionDt,
        Target.ActionID = Source.ActionID
   WHEN NOT MATCHED THEN
    INSERT(
        Status,Name,Code,ActionDt,ActionID
    )VALUES(
        Source.Status,
        Source.Name,
        Source.Code,
        Source.ActionDt,
        Source.ActionID
    )
    OUTPUT inserted.RedIC,$action as Action;

我假设Coldfusion可以使用此结果集。如果不是,请切换到OUTPUT的变体,该变体代替填充表变量(OUTPUT ... INTO),然后使用该变量来设置要添加到过程中的OUTPUT参数。