简体   繁体   English

根据 rowversion 值更新记录?

[英]Update record based on rowversion value?

I have recently implemented SQL rowversion to prevent concurrency issues in my system.我最近实施了 SQL rowversion以防止系统中出现并发问题。 I use rowversion in where clause when updating single rows in the tables.更新表中的单行时,我在 where 子句中使用rowversion So far I have tested and seems like a good solution.到目前为止,我已经测试过,似乎是一个很好的解决方案。 Now I'm looking for an easy way to implement this feature in my system.现在我正在寻找一种在我的系统中实现此功能的简单方法。 Here is SP that runs when user wants to update the record:这是当用户想要更新记录时运行的 SP:

CREATE PROCEDURE [dbo].[UpdateBuilding]
    @Status BIT = NULL,
    @Name VARCHAR(50) = NULL,
    @Code CHAR(2) = NULL,
    @OriginalRowVersion ROWVERSION
AS
    SET NOCOUNT ON
    SET XACT_ABORT ON
    BEGIN
        UPDATE dbo.Building
        SET Status = @Status,
            Name = @Name,
            Code = @Code,
            ActionDt = CURRENT_TIMESTAMP
        WHERE RowVersion = @OriginalRowVersion
        IF @@ROWCOUNT = 0
        BEGIN
            RAISERROR('Buildingwith code %s was modified or deleted by another user.', 16, 1, @Code);
        END;
    END;

If I want to execute SP above I would need to pass required parameters.如果我想在上面执行 SP,我需要传递所需的参数。 This is how I call SP in SQL Management Studio:这是我在 SQL Management Studio 中调用 SP 的方式:

EXEC UpdateBuilding
    @Status = 1,
    @Name = "Rockefeller Center",
    @Code = 436,
    @OriginalRowVersion = 0x0000000000006955;

Now I started looking how to implement this in my system where I use ColdFusion to communicate with Datatbase.现在我开始研究如何在我使用 ColdFusion 与 Datatbase 通信的系统中实现这一点。 Here is example on how this procedure will be executed with CF 2016:以下是有关如何使用 CF 2016 执行此过程的示例:

<cfstoredproc procedure="UpdateBuilding" datasource="#dsn#">
    <cfprocparam dbvarname="@Status" value="#trim(arguments.status)#" cfsqltype="cf_sql_bit" />
    <cfprocparam dbvarname="@Code" value="#trim(arguments.code)#" cfsqltype="cf_sql_char" maxlength="2" null="#!len(trim(arguments.code))#" />
    <cfprocparam dbvarname="@Name" value="#trim(arguments.name)#" cfsqltype="cf_sql_varchar" maxlength="50" null="#!len(trim(arguments.name))#" />
    <cfprocresult name="Result"/>
</cfstoredproc>

You can see that all values are passed with arguments that user submits in the form.您可以看到所有值都与用户在表单中提交的参数一起传递。 However, updating based on PK value (Code column in my case) was pretty simple.但是,基于PK值(在我的情况下为代码列)的更新非常简单。 Now I have binary value and that makes everything more complicated.现在我有了二进制值,这让一切变得更加复杂。 First I use JSON to send the data to client side.首先,我使用 JSON 将数据发送到客户端。 Sending rowversion in JSON object would require converting that value to binary and then converting back when user submits the form.在 JSON 对象中发送rowversion需要将该值转换为binary ,然后在用户提交表单时转换回来。 I'm wondering if there is better way to achieve this?我想知道是否有更好的方法来实现这一目标? Ideally I would not even send rowversion value to the user side.理想情况下,我什至不会向用户端发送rowversion值。 I woul keep that on the back end and once user submits the form pull row version value based on PK then call stored procedure.我会将其保留在后端,一旦用户提交基于 PK 的表单拉行版本值,然后调用存储过程。 If anyone knows good way to handle this kind of situations please let me know.如果有人知道处理这种情况的好方法,请告诉我。 I have not used rowversion before and this is new to me.我以前没有使用过rowversion ,这对我来说是新的。

I use a similar approach where I have a column named version of type int . 我使用类似的方法,其中有一列名为type int version I pass that to the client in any read operation. 我通过任何读取操作将其传递给客户端。 If the client updates a record, it must send back the version of the record being updating, and the update will increment the version number. 如果客户端更新了一条记录,则它必须发回正在更新的记录的版本,并且更新将增加版本号。 However, my approach sets a ColdFusion lock instead of a DB lock. 但是,我的方法设置了ColdFusion锁而不是DB锁。 Here's some simplified logic: 这是一些简化的逻辑:

function updateRecord (
    required numeric recordID,
    required struct updateData,
    required numeric version) {

    lock name="#arguments.recordID#" type="exclusive" timeout="1" throwontimeout=true {
        qRecord = queryExecute() // get the record
        if (qRecord.recordCount != 1) {
            throw();
        }
        if (qRecord.version != arguments.version) {
            throw();
        }
        // do the update using arguments.updateData
    }

}

One issue with this is that other nodes in a cluster would not be aware of the named lock. 与此相关的一个问题是,群集中的其他节点将不知道该命名锁。 You would have to come up with another way of locking that section of code to other requests across the cluster. 您将不得不想出另一种将代码段锁定到集群中其他请求的方法。 There are ways to do it, as described in this thread: 如该线程中所述,有多种方法可以做到:

https://dev.lucee.org/t/distributed-lock-management/1004 https://dev.lucee.org/t/distributed-lock-management/1004

And if this is an issue, I'm sure there are other solutions available. 如果这是一个问题,我敢肯定还有其他解决方案可用。

Dealing with binary isn't as difficult as you may be thinking. 处理二进制文件并不像您想的那样困难。 After you retrieve the binary value, encode it as a string suitable for sending to the client (either hex or base64): 检索二进制值后,将其编码为适合发送给客户端的字符串(十六进制或base64):

<cfset hexStringForClient = binaryEncode(queryName.theBinaryColumn, 'hex')>

When the client side returns the encoded string, decode it back into binary and use cf_sql_binary to pass it to the database: 当客户端返回编码后的字符串时, 解码回二进制并使用cf_sql_binary将其传递给数据库:

<cfset binaryValue = binaryDecode(hexStringFromClient, 'hex')>

<cfstoredproc procedure="UpdateBuilding" datasource="#dsn#">
    ... 
    <cfprocparam dbvarname="@OriginalRowVersion" value="#binaryValue#" cfsqltype="cf_sql_binary" />
    ....
</cfstoredproc>

Side note, as mentioned in your other thread , the optimistic concurrency check uses both the primary key and rowversion value in the WHERE clause: 旁注, 如您在其他线程中所述 ,开放式并发检查使用WHERE子句中的主键和行版本值:

WHERE
    Code = @Code
    AND RowVersion = @OriginalRowVersion;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM