So I have a patientinfo
table where I'm inserting a patient with a click of a button
.
所以我有一个PatientInfo表,我只需点击一个按钮就可以插入一个患者。
That same button, generates the ID of the patient but for some reason, if I run a timer to execute this code from 5 apps at the exact same time, only one will go through, while the other ones will fail. Although I have tried to use ROWlock
in this query, Updlock
, and even Tablockx
.
同样的按钮会生成患者的ID,但出于某种原因,如果我运行计时器同时从5个应用程序执行此代码,则只有一个应用程序会通过,而其他应用程序将失败。尽管我已经尝试在这个查询中使用rowlock、updlock,甚至Tablockx。
Any thoughts on what could be causing that? There are no errors aside from the Primary key error
when the insertion fails because I'm inserting the same generated ID.
有没有想过是什么导致的?当插入失败时,除了主键错误之外,没有其他错误,因为我插入了相同的生成ID。
Private Function GetNextPatientId(labID As String) As String
Dim connectionString As String = My.Settings.PatientsConnectionString
Dim result As String = ""
Using connection As New SqlConnection(connectionString)
connection.Open()
Dim retryCount As Integer = 0
Dim maxRetries As Integer = 3
Dim delayMilliseconds As Integer = 400
Using transaction As SqlTransaction = connection.BeginTransaction()
While retryCount < maxRetries
Try
Dim queryString As String = "
BEGIN TRAN
DECLARE @nextPatientId AS VARCHAR(12);
SELECT TOP 1 @nextPatientId = (CAST(SUBSTRING(PatientID, 3, 12) AS BIGINT) + 1)
FROM patientinfo WITH (UPDLOCK)
WHERE SUBSTRING(patientid, 1, 2) = @labID
ORDER BY SUBSTRING(PatientID, 3, 12) DESC;
IF @nextPatientId IS NULL
SET @nextPatientId = '01';
SELECT @nextPatientId AS nextPatientIdValue;
COMMIT TRAN;"
Dim command As New SqlCommand(queryString, connection, transaction)
command.Parameters.AddWithValue("@labID", labID)
Dim nextPatientIdValue As String = ""
Using reader As SqlDataReader = command.ExecuteReader()
If reader.Read() Then
nextPatientIdValue = Convert.ToString(reader("nextPatientIdValue"))
End If
End Using
result = nextPatientIdValue
Exit While
Catch ex As SqlException When ex.Number = 1205 AndAlso retryCount < maxRetries
retryCount += 1
Threading.Thread.Sleep(delayMilliseconds)
Catch ex As Exception
transaction.Rollback()
Throw
End Try
End While
transaction.Commit()
End Using
End Using
Return result
End Function
更多回答
I would recommend just using IDENTITY
instead of this scheme but WITH (UPDLOCK, HOLDLOCK)
might work. As it will lock the range and hold the lock until the end of the transaction
我建议只使用IDENTITY而不是这个方案,但WITH(IDENTITY,HOLDIDENTITY)可能会起作用。因为它将锁定范围并保持锁直到事务结束
I'll give it a try. Any reason on why the combination you mentioned would work but TABLOCKX wouldn't?
我要试一试。为什么你提到的组合可行,而TABLOCKX不行?
TABLOCKX
doesn't mention that the locks will be held until the end of the transaction and presumably they aren't from the behaviour you describe learn.microsoft.com/en-us/sql/t-sql/queries/…
TABLOCKX没有提到这些锁将一直保持到事务结束,而且它们大概不是您所描述的learn.microsoft.com/en-us/sql/t-sql/queries/…的行为。
Okay so using updlock holdlock didn't really do the trick. I have a question though. It should be totally fine that the insert and SELECT are not in the same query. Correct?
好吧,所以使用锁死锁并没有真正起到作用。不过,我有一个问题。INSERT和SELECT不在同一个查询中应该完全没有问题。对,是这样?
You should put your SELECT generating nextPatientIdValue and INSERT in patientinfo
inside a common transaction for the UPDLOCK to have any effect. Currently second app waits only until COMMIT is executed by the first app and gets the same MAX(PatientID) from patientinfo
table as the first app has not INSERTed anything there yet.
您应该将生成nextPatientIdValue的SELECT和INSERT放在一个公共事务中,才能使UPDLOCK生效。目前,第二个应用程序只等待第一个应用程序执行提交,并从PatientInfo表中获得相同的Max(PatientID),因为第一个应用程序还没有在那里插入任何内容。
我是一名优秀的程序员,十分优秀!