【问题标题】:Merge Insert/Update prevent PK violations and deadlocks?合并插入/更新防止 PK 违规和死锁?
【发布时间】:2019-01-27 18:42:59
【问题描述】:

我现在有一个存储过程来处理我的一张表中的插入和更新事务。我仍在测试此解决方案是否存在任何潜在问题以及如何改进该过程。这个 SP 需要很少的参数,然后检查匹配的 ID 并执行插入或更新。我读过这个Article 关于primary key violation error, showing that MERGE is vulnerable to concurrency problems like a multi-statement conditional INSERT/UPDATE。似乎他们已经使用WITH (HOLDLOCK) 解决了一些问题。我是存储过程和合并世界的新手。如果这对于高交易量的应用程序来说是可靠的代码,我想在这里发表您的意见?我可能有多个用户在同一个表中插入或同时运行更新语句。在这种情况下,参数嗅探是否存在任何潜在问题?如果是我应该考虑使用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 相同,或者如果不存在,那么将像这样为 Insert @ 987654328@ 或像这样更新SELECT @RecordID AS RecID)。如果有人有任何建议并知道从运行插入/更新并合并的 SP 返回 RecID 的最佳方式,请告诉我。

【问题讨论】:

  • 我发现顶部的叙述有点难以拆开,所以我看不出那里是否有问题(如果有,你能更清楚地把事情分成几段,让清楚你在问什么)。我已经解决了您似乎在底部询问的问题。如果顶部实际上是一个问题并且与您在底部提出的问题无关,我建议将其分成两个问题。 (请把这个放在底部,因为我已经回答过了)

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


【解决方案1】:

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

您可以将OUTPUT clause 添加到您的MERGE 语句中。这将允许您返回一个包含新 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 参数。

【讨论】:

  • 我已经测试过了,OUTPUT 成功返回了RecIDAction。你对这个过程有什么想法吗?这种交易方式有没有cons?谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-10
  • 2016-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多