【问题标题】:VB / C# SQL Server Record LockingVB / C# SQL Server 记录锁定
【发布时间】:2018-02-02 11:48:19
【问题描述】:

我一遍又一遍地搜索这个问题,但找不到答案。我想在sqlserver中读取并锁定一条记录,以某种方式处理数据,然后将记录写回数据库并释放锁。

这是我尝试使用的代码类型的示例:

Imports System.Data.SqlClient

Public Module TestDB

Private Sub DoThis()

    ProcessData(1, 1)

End Sub

Private Sub ProcessData(ID As Integer, Quantity As Integer)

    Dim DBMS As New DB
    Dim MyRow As DataRow = Nothing

    DBMS.OpenDatabase()

    MyRow = DBMS.GetRecord(ID)          'lock this record so nobody else can read it, but only the record, not the table
    If MyRow IsNot Nothing Then
        '----------
        'do some processing
        MyRow("Quantity") = MyRow("Quantity") + Quantity
        '----------
        DBMS.UpdateRecord(MyRow)        'unlock this record people can read it again
    End If

    DBMS.CloseDatabase()

End Sub

End Module


Public Class DB

Public m_oConnection As SqlConnection
Public m_oTransaction As SqlTransaction
Public m_oCommand As SqlCommand
Public m_oDataAdapter As SqlDataAdapter
Public m_oDataTable As DataTable

Public Shared m_sConnectionString As String = "Server=SQL01; Database=MyDB; uid=me; pwd=secret;"

Public Sub OpenDatabase()

    m_oConnection = New SqlConnection
    m_oConnection.ConnectionString = m_sConnectionString
    m_oConnection.Open()

End Sub

Public Sub CloseDatabase()

    m_oConnection.Close()

End Sub

Public Function GetRecord(RecordID As Integer) As DataRow

    Dim Result As DataRow = Nothing

    Dim SQL As String = ""
    SQL &= "SELECT * FROM TempStock WHERE StockID = " & RecordID

    m_oDataAdapter = New SqlDataAdapter
    m_oDataTable = New DataTable

    m_oCommand = New SqlCommand(SQL, m_oConnection)

    m_oDataAdapter.SelectCommand = m_oCommand
    m_oDataAdapter.Fill(m_oDataTable)

    Dim iRows As Integer = m_oDataTable.Rows.Count
    If iRows > 0 Then
        Result = m_oDataTable.Rows(0)
    End If

    Return Result

End Function

Public Function UpdateRecord(Row As DataRow) As Integer

    Dim Result As Integer = 0

    Dim SQL As String = ""
    SQL &= "UPDATE TempStock "
    SQL &= "SET Quantity = " & Row("Quantity")
    SQL &= "WHERE StockID = " & Row("StockID")

    m_oCommand = New SqlCommand(SQL, m_oConnection)
    Dim iRows As Integer = m_oCommand.ExecuteNonQuery()

    Return Result

End Function

End Class

在我看来这是一个非常简单的想法,并且是在多用户应用程序中编写的一个非常标准的东西 - 我不希望 2 个用户读取相同的记录并尝试更新它。 我不想涉及时间戳等,到目前为止,使用事务我无法让它工作 - 除非我完全误解了它们。

有人可以帮忙吗? (我的示例是在 VB 中,但 C# 的答案同样有用)。

【问题讨论】:

    标签: c# sql vb.net locking


    【解决方案1】:

    [这确实是一条评论,但太长了]

    ADO.Net 非常偏向于乐观并发,即没有实际锁定,而是使用时间戳(或检查当前行值)来检测更改,然后您必须处理这些更改。 MS 会告诉你,大多数时候这是一个更好的策略,例如,因为用户可以去吃午饭,留下锁以防止其他用户做任何事情,等等。
    事务不做锁定,它们只是保证一个批次的全有或全无执行。这是一个常见的、直观的误解 - 例如,请参阅 Is a single SQL Server statement atomic and consistent?,它希望还包含有关如何使锁定发生的足够信息,至少可以帮助您入门。

    针对 OP 的评论/答案进行编辑:

    您需要更多地研究锁定 - ROWLOCK、UPDLOCK、事务隔离级别等,特别是如果您打算使用悲观锁定。正如我所提到的,链接中的回复中有一些很好的材料可以帮助您入门。此外,David Sceppa 的 ADO.Net 2.0 书 - 确实有 10 多年的历史了 - 仍然具有相关性,并在 p507 和 650 上简要介绍了这个问题。 也不要放弃乐观锁定的希望——您可能会发现明智地使用事务等可以获得令人惊讶的长路。即使使用乐观锁定,您假定的负库存等情况也绝对可以避免;但是一个用户或另一个用户会认为他们可以继续,但随后会收到一条错误消息,表明他们的更新失败。

    【讨论】:

      【解决方案2】:

      我同意,在大多数情况下,乐观锁定都有效,如果允许使用时间戳,那么当有人可能更改您的记录时,它会有所帮助。

      我遇到的问题是遗留系统,从头开始完全重写和更好地设计的时间不会发生。

      遗留代码使用随机访问二进制文件,在某些时候,MS 非常友好地更改了允许访问 .net 中的文本/随机/二进制文件的库,并停止了多用户访问工作。文档说这一切都有效,但很简单,它没有 - 您可以打开具有共享访问权限的文件并锁定记录,但它会锁定文件,而不仅仅是记录。

      为了解决这个问题,我需要将数据迁移到数据库并替换类中的一些子/函数,以便它们从数据库而不是二进制文件中获取数据。一切都很好,并且代码更改最少……除非涉及使用我上面描述的机制的特定部分。

      愚蠢的是,在我看来,这是非常基本的业务数据处理......阅读库存记录,看看是否有足够的库存来完成订单,然后保留或扣除库存。如果另一个用户同时尝试做同样的事情,他们将看到相同的数量可用并尝试使用它,如果数量如此,那么库存将变为负数和/或某人将无法拥有他们的订单,因为库存不足。 显然,我不想继续阅读记录并刷新屏幕,以便用户可以看到每秒的库存变化。 除此之外,还有一个服务/后台任务自动进行库存处理——它只在一个服务器上运行,所以不会有与自身竞争的问题,但是用户可以手动处理库存,以处理失败的订单出于某种原因的自动过程。

      到目前为止,我管理的最好的方法是使用交易(这可能很好,因为我想在“全有或全无”的基础上处理订单的多个项目),但到目前为止,我唯一的可以找到正在使用:

      打开表、打开事务、选择、(处理)、更新、提交或终止、关闭表

      使用 TABLOCKX,但这会锁定整个表。

      但是如果没有这个,在更新命令发出之前,其他用户可以根据自己的喜好重复选择语句,并且他们看到的是原始图,而不是更新后的图。直到它发出的提交命令才会出现。

      【讨论】:

        【解决方案3】:

        好的,对于那些可能感兴趣的人,我终于设法解决了这个问题 - 我的测试代码如下。

        有一个包含各种“数据库”例程的模块,以及一个调用这些例程的表单(请注意,表单代码只是代码,不是实际的表单,但应该足以演示它是如何工作的)。

        通过使用代码中显示的事务和锁定提示,我可以读取记录并将其锁定,以便其他用户无法访问它。我可以应用更新,一旦应用提交(或中止)事务,记录就会为其他用户解锁。

        尝试访问锁定记录的第二个用户/实例将被“暂停”,直到第一个实例的事务完成(尽管这将在 30 秒后超时,但这由代码处理)。

        第二个实例可以访问不同的记录并根据需要对其进行更新。

        模块代码:

        Option Explicit On
        
        Imports System
        Imports System.Data.SqlClient
        
        Public Module DataAccess
        
        Public m_sConnectionString As String = "Server=sql01; Database=test; uid=myuser; pwd=mypwd;"
        Private m_oConnection As SqlConnection = New SqlConnection
        Private m_oTransaction As SqlTransaction
        
        Public Function OpenDB() As Boolean
        
            Try
                m_oConnection = New SqlConnection
                m_oConnection.ConnectionString = m_sConnectionString
                m_oConnection.Open()
        
                Return True
        
            Catch sqlex As SqlException
                Console.WriteLine(sqlex.Message)
                Return False
        
            Catch ex As Exception
                Console.WriteLine(ex.Message)
                Return False
        
            End Try
        
        End Function
        
        Public Sub CloseDB()
        
            Try
                m_oTransaction = Nothing
                m_oConnection.Close()
                m_oConnection = Nothing
        
            Catch sqlex As SqlException
                Console.WriteLine(sqlex.Message)
                Stop
        
            Catch ex As Exception
                Console.WriteLine(ex.Message)
                Stop
        
            Finally
        
            End Try
        
        End Sub
        
        Public Sub CommitTransaction()
        
            Try
                If m_oTransaction IsNot Nothing Then m_oTransaction.Commit()
        
            Catch sqlex As SqlException
                Console.WriteLine(sqlex.Message)
                Stop
        
            Catch ex As Exception
                Console.WriteLine(ex.Message)
                Stop
        
            Finally
                m_oTransaction = Nothing
        
            End Try
        
        End Sub
        
        Public Sub AbortTransaction()
        
            Try
                If m_oTransaction IsNot Nothing Then m_oTransaction.Rollback()
        
            Catch sqlex As SqlException
                Console.WriteLine(sqlex.Message)
                Stop
        
            Catch ex As Exception
                Console.WriteLine(ex.Message)
                Stop
        
            Finally
                m_oTransaction = Nothing
        
            End Try
        
        End Sub
        
        Public Function ReadRecordByID(ID As Integer, LockRecord As Boolean) As Tuple(Of Boolean, DataRow)
        
            Dim Result As Tuple(Of Boolean, DataRow) = New Tuple(Of Boolean, DataRow)(False, Nothing)
        
            If ID = 0 Then Return Result
        
            Dim sSQL As String = ""
            Dim oCommand As SqlCommand = Nothing
            Dim LockCommand As String = ""
        
            Dim MyDA As SqlDataAdapter
            Dim MyTable As DataTable
            Dim MyRow As DataRow
        
            Try
                m_oTransaction = Nothing
        
                oCommand = m_oConnection.CreateCommand
                If LockRecord Then
                    m_oTransaction = m_oConnection.BeginTransaction(IsolationLevel.Serializable)
                    LockCommand = "WITH (HOLDLOCK, UPDLOCK) "
                End If
        
                sSQL = ""
                sSQL &= "SELECT * FROM TempStock " & LockCommand & "WHERE StockID = " & ID
        
                oCommand.CommandText = sSQL
                oCommand.Connection = m_oConnection
        
                MyDA = New SqlDataAdapter
                MyTable = New DataTable
        
                If LockRecord Then oCommand.Transaction = m_oTransaction
        
                MyDA.SelectCommand = oCommand
                MyDA.Fill(MyTable)
                MyRow = MyTable.Rows(0)
        
                Result = New Tuple(Of Boolean, DataRow)(True, MyRow)
        
                MyTable = Nothing
                MyDA = Nothing
        
            Catch sqlex As SqlException
                Console.WriteLine(sqlex.Message)
                If m_oTransaction IsNot Nothing Then AbortTransaction()
        
            Catch ex As Exception
                Console.WriteLine(ex.Message)
                If m_oTransaction IsNot Nothing Then AbortTransaction()
        
            Finally
        
            End Try
        
            Return Result
        
        End Function
        
        Public Function UpdateRecord(Row As DataRow) As Boolean
        
            Dim Result As Boolean = False
        
            Dim sSQL As String = ""
            Dim oCommand As SqlCommand = Nothing
        
            Try
                oCommand = m_oConnection.CreateCommand
        
                sSQL = ""
                sSQL &= "UPDATE TempStock " & vbCrLf
                sSQL &= "SET Quantity = " & Row("Quantity") & ", Allocated = " & Row("Allocated") & ", LastUpdated = GETDATE() WHERE StockID = " & Row("StockID")
        
                oCommand.CommandText = sSQL
                oCommand.Connection = m_oConnection
                oCommand.Transaction = m_oTransaction
                oCommand.ExecuteNonQuery()
        
                Result = True
        
            Catch sqlex As SqlException
                Console.WriteLine(sqlex.Message)
                If m_oTransaction IsNot Nothing Then AbortTransaction()
                Result = False
        
            Catch ex As Exception
                Console.WriteLine(ex.Message)
                If m_oTransaction IsNot Nothing Then AbortTransaction()
                Result = False
        
            End Try
        
            Return Result
        
        End Function
        
        End Module
        

        表格代码:

        Option Explicit On
        
        Imports System
        
        Public Class Form1
        
        Private MyDataRow As DataRow
        
        Private Sub btnOpen_Click(sender As Object, e As EventArgs) Handles btnOpen.Click
        
            Dim ID As Integer = Val(txtID.Text)
            If ID < 1 Then Exit Sub
        
            btnOpen.Enabled = False
            btnUpdate.Enabled = False
            btnCommit.Enabled = False
            btnAbort.Enabled = False
        
            If OpenDB() Then
        TryAgain:
                Dim ReadRow As Tuple(Of Boolean, DataRow) = ReadRecordByID(ID, chkTransaction.Checked)
                btnUpdate.Enabled = True
                If ReadRow.Item1 Then
                    MyDataRow = ReadRow.Item2
                    lblQty.Text = MyDataRow("Quantity")
                    lblAlloc.Text = MyDataRow("Allocated")
                    txtQty.Text = ""
                    txtAlloc.Text = ""
                    If Not chkTransaction.Checked Then btnAbort.Enabled = True
                Else
                    Select Case MessageBox.Show("Transaction Time Out - Unable to lock record", "Database", MessageBoxButtons.RetryCancel, MessageBoxIcon.Question)
                        Case DialogResult.Retry
                            GoTo TryAgain
                        Case Else
                    End Select
                    btnOpen.Enabled = True
                    btnUpdate.Enabled = False
                    btnCommit.Enabled = False
                    btnAbort.Enabled = False
                    txtID.Select()
                End If
            Else
                btnOpen.Enabled = True
                btnUpdate.Enabled = False
                btnCommit.Enabled = False
                btnAbort.Enabled = False
            End If
        
        End Sub
        
        Private Sub btnUpdate_Click(sender As Object, e As EventArgs) Handles btnUpdate.Click
        
            MyDataRow("Quantity") += Val(txtQty.Text)
            MyDataRow("Allocated") += Val(txtAlloc.Text)
        
            If UpdateRecord(MyDataRow) Then
        
                If chkTransaction.Checked Then
                    btnCommit.Enabled = True
                    btnAbort.Enabled = True
                    btnUpdate.Enabled = False
                Else
                    btnOpen.Enabled = True
                    btnCommit.Enabled = False
                    btnAbort.Enabled = False
                    btnUpdate.Enabled = False
        
                    lblQty.Text = ""
                    lblAlloc.Text = ""
                    txtQty.Text = ""
                    txtAlloc.Text = ""
                End If
        
            Else
                btnOpen.Enabled = True
                btnUpdate.Enabled = False
                btnCommit.Enabled = False
                btnAbort.Enabled = False
        
                lblQty.Text = ""
                lblAlloc.Text = ""
                txtQty.Text = ""
                txtAlloc.Text = ""
            End If
        
        End Sub
        
        Private Sub btnCommit_Click(sender As Object, e As EventArgs) Handles btnCommit.Click
        
            CommitTransaction()
        
            lblQty.Text = ""
            lblAlloc.Text = ""
            txtQty.Text = ""
            txtAlloc.Text = ""
        
            btnOpen.Enabled = True
            btnCommit.Enabled = False
            btnAbort.Enabled = False
        
            CloseDB()
        
        End Sub
        
        Private Sub btnAbort_Click(sender As Object, e As EventArgs) Handles btnAbort.Click
        
            AbortTransaction()
        
            lblQty.Text = ""
            lblAlloc.Text = ""
            txtQty.Text = ""
            txtAlloc.Text = ""
        
            btnOpen.Enabled = True
            btnUpdate.Enabled = False
            btnCommit.Enabled = False
            btnAbort.Enabled = False
        
            CloseDB()
        
        End Sub
        
        End Class
        

        为了让它发挥作用,我进行了很多试验和错误,因为我找不到任何示例,只是以模糊的方式涵盖了理论的文档。

        我确实让它工作了,然后它停止了 - 我更改了数据库表 - 你必须在表上有一个主键,并且有 'allow row locks = true'。

        我希望这对处于类似情况的任何人有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多