Обновить запись на основе значения rowversion? - PullRequest
0 голосов
/ 27 августа 2018

Я недавно внедрил SQL rowversion для предотвращения проблем параллелизма в моей системе. Я использую rowversion в предложении where при обновлении отдельных строк в таблицах. До сих пор я проверил и кажется хорошим решением. Сейчас я ищу простой способ реализовать эту функцию в моей системе. Вот 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;

Если я хочу выполнить SP выше, мне нужно будет передать необходимые параметры. Вот как я называю SP в SQL Management Studio:

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

Теперь я начал искать, как реализовать это в моей системе, где я использую ColdFusion для связи с базой данных. Вот пример того, как эта процедура будет выполняться с 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>

Вы можете видеть, что все значения передаются с аргументами, которые пользователь отправляет в форме. Однако обновление на основе значения PK (в моем случае столбец «Код») было довольно простым. Теперь у меня есть двоичное значение, и это все усложняет. Сначала я использую JSON для отправки данных на клиентскую сторону. Отправка rowversion в объект JSON потребует преобразования этого значения в binary, а затем обратного преобразования при отправке формы пользователем. Мне интересно, есть ли лучший способ добиться этого? В идеале я бы даже не отправил rowversion значение на сторону пользователя. Я оставлю это на бэкэнде, и как только пользователь отправит значение версии строки для извлечения формы на основе PK, затем вызову хранимую процедуру. Если кто-нибудь знает хороший способ справиться с такими ситуациями, пожалуйста, дайте мне знать. Я не использовал rowversion раньше, и это ново для меня.

Ответы [ 2 ]

0 голосов
/ 28 августа 2018

Работа с двоичным файлом не так сложна, как вы думаете. После получения двоичного значения кодирует в виде строки, подходящей для отправки клиенту (в шестнадцатеричном или base64):

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

Когда клиентская сторона возвращает закодированную строку, декодирует обратно в двоичный файл и использует cf_sql_binary для передачи ее в базу данных:

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

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

Примечание: , как уже упоминалось в другом потоке , оптимистическая проверка параллелизма использует и первичный ключ, и значение обращения к строке в предложении WHERE:

WHERE
    Code = @Code
    AND RowVersion = @OriginalRowVersion;
0 голосов
/ 27 августа 2018

Я использую аналогичный подход, когда у меня есть столбец с именем version типа int. Я передаю это клиенту в любой операции чтения. Если клиент обновляет запись, он должен отправить обратно версию обновляемой записи, и обновление будет увеличивать номер версии. Однако мой подход устанавливает блокировку ColdFusion вместо блокировки БД. Вот упрощенная логика:

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
    }

}

Одна из проблем заключается в том, что другие узлы в кластере не будут знать о названной блокировке. Вам нужно будет придумать другой способ привязки этого раздела кода к другим запросам в кластере. Есть способы сделать это, как описано в этой теме:

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

И если это проблема, я уверен, что есть другие доступные решения.

...