SQL Server存储过程同时更新记录和返回值

时间:2017-08-01 18:51:43

标签: c# sql-server stored-procedures coldfusion

提前致谢; 我继承了一个存储过程,它增加一条记录并在一次调用中返回它的值。目的是仅返回单个值,就像Identity()列一样。 这是存储过程:

ALTER PROCEDURE [dbo].[sp_GetNextKey]
  @RetVal int OUTPUT,
  @Name varchar(250)
AS
  UPDATE Keys
  SET Key_Next = Key_Next + 1,
       @RetVal = Key_Next + 1
  FROM Keys
  WHERE Key_Table = @Name

不幸的是,这是非常古老的代码,我无法修改应用程序来实现Identity()。这已经在生产中工作多年,仅由ColdFusion应用程序访问。它现在由C#应用程序调用,我们正在获得似乎是线程问题。我需要在SQL中而不是在CF或.NET中解决这个问题,因为在两个应用程序中的大量位置都会调用此过程。

它们返回的值相同,就像它们生活在独立环境中一样。我应该补充一点,它确实在大多数情况下按预期工作,但并非总是如此。我的猜测是,只有在每次应用程序中以完全相同的毫秒调用它时,它才会受到严重负载。

我讨厌做某种锁定,因为这个东西每小时被召唤数千次。我担心我们最终会遇到死锁问题。

为清楚起见,这里是CF和C#中的调用:

<CFFUNCTION name="getNextId" returntype="numeric" access="public">
  <CFARGUMENT name="keyTableName" type="string" required="yes" >
  <CFARGUMENT name="dbSource"     type="string" required="yes" >
  <cfstoredproc procedure="sp_GetNextKey" datasource="#ARGUMENTS.dbSource#" returnCode="No">
    <cfprocparam type="OUT" CFSQLType="CF_SQL_INTEGER" variable="RetVal">
    <cfprocparam type="IN" CFSQLType="CF_SQL_VARCHAR" value="#UCase(ARGUMENTS.keyTableName)#" maxlength="250">
  </cfstoredproc>
  <cfquery name="ab" datasource="#ARGUMENTS.dbSource#">
    SET ARITHABORT ON
  </cfquery>
  <cfreturn RetVal >
</CFFUNCTION>

C#:

SqlCommand cmd = new SqlCommand("sp_GetNextKey", conn);
cmd.CommandType = CommandType.StoredProcedure;

var outParam = new SqlParameter("@RetVal", SqlDbType.Int);
outParam.Direction = ParameterDirection.Output;
outParam.Size = 128;
cmd.Parameters.Add(outParam);

cmd.Parameters.Add("@Name", SqlDbType.VarChar);
cmd.Parameters["@Name"].Value = tableName;

conn.Open();
cmd.ExecuteNonQuery();
//get the return value
retVal = Convert.ToInt32(cmd.Parameters["@RetVal"].Value);
conn.Close();

允许此事件为任何调用它的应用程序返回唯一值的最佳方法是什么?

根据评论,我们尝试了这一点,但没有改变结果:

ALTER 
PROCEDURE [dbo].[sp_GetNextKey]
  @RetVal int OUTPUT,
  @Name varchar(250)
AS
  UPDATE Keys
  WITH (ROWLOCK)
  SET @RetVal = Key_Next = Key_Next + 1
  FROM Keys
  WHERE Key_Table = @Name

也试过但没有用:     ALTER PROCEDURE [dbo].[sp_GetNextKey] @RetVal int OUTPUT, @Name varchar(250) AS BEGIN TRANSACTION EXEC sp_getapplock @LockMode = 'Shared', @Resource = 'Keys'; UPDATE Keys WITH (ROWLOCK) SET @RetVal = Key_Next = Key_Next + 1 FROM Keys WHERE Key_Table = @Name EXEC sp_releaseapplock @Resource = 'Keys' COMMIT TRANSACTION

3 个答案:

答案 0 :(得分:2)

试试这个:

ALTER PROCEDURE [dbo].[sp_GetNextKey]
  @RetVal int OUTPUT,
  @Name varchar(250)
AS
  BEGIN TRAN
      SELECT @RetVal = MAX(Key_Next) + 1 FROM Keys WHERE Key_Table = @Name
      UPDATE Keys SET Key_Next = @RetVal WHERE Key_Table = @Name
  COMMIT

这有意将两个操作拆分为单独的语句,并将它们包装在一个事务中,以便使用正确的锁并保持一致性。

答案 1 :(得分:1)

试试吧

ALTER PROCEDURE [dbo].[sp_GetNextKey]
  @RetVal int OUTPUT,
  @Name varchar(250)
AS
  UPDATE Keys
  SET @RetVal = Key_Next = Key_Next + 1
  FROM Keys
  WHERE Key_Table = @Name

您需要在更新字段的同时设置变量的值。

答案 2 :(得分:0)

函数中的冷合并并发错误:

如果将具有ColdFusion getNextId函数的组件缓存在内存中,则可能会引起问题,因为非显式范围变量(例如RetVal变量)的默认作用域是组件级别,而不是函数一个不熟悉CF的人可能会认为,这使其对于组件是全局的。在函数中的参数之后添加以下行,以使其不再容易受到并发问题的影响(将变量定义为函数的局部变量。)

<cfset var RetVal />

或者,只要在变量出现在函数中时,都可以在变量前加上local.作为前缀,以明确地将变量作用于函数。例如:local.RetVal。 (适用于CF9 +)