2017-08-01 89 views
0

先谢谢了; 我继承了一个存储过程,它在一次调用中增加一条记录并返回其值。意图是只返回一个单一的值,就像一个Identity()列。 这里是存储过程:SQL Server存储过程更新记录和返回值同时

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 

不幸的是,这是很老的代码,我不能修改应用程序中实现身份()。这在多年以来一直由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

+0

你什么意思,你不能修改使用身份申请?你只能修改sql server中的表,并且可以将seed设置为max(Key)值。或者,也许你想看看NEWID(),但无论哪种方式我很困惑,为什么应用程序会被修改。我也猜测Keys对它没有独特的约束? – scsimon

+0

查看隔离级别和ROWLOCK。 – pmbAustin

+0

proc不应该有并发问题,但可以重构为'SET @RetVal = Key_Next = Key_Next + 1'。重要的是,'KeyTable'应该是主键。 –

回答

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 

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

+1

这与问题中发布的问题代码有何不同? –

+0

在代码中,您试图设置变量值,就像您在select语句上进行操作时一样,但是当您更新字段时,需要在更新数据库字段的同时更新变量。 –

+0

我没有意识到SET元素并不都是同时发生的,就像SELECT一样。这有点酷。 – Shawn

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 

这有意分裂两个操作成单独的语句,它们包装在事务,使得正确的锁应使用和一致性维持。

+2

这是如何处理并发问题与原始代码不同的?存储过程已经隐含在自己的事务中运行。 –