2017-08-01 108 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

這是如何處理併發問題與原始代碼不同的?存儲過程已經隱含在自己的事務中運行。 –