Эффективная транзакция, блокировка записи - PullRequest
5 голосов
/ 22 февраля 2009

У меня есть хранимая процедура, которая отбирает 1 запись обратно. хранимая процедура может быть вызвана из нескольких разных приложений на разных ПК. Идея состоит в том, что хранимая процедура возвращает следующую запись, которую необходимо обработать, и если два приложения вызывают хранимый процесс одновременно, одну и ту же запись возвращать не следует. Мой запрос ниже, я пытаюсь написать запрос максимально эффективно (sql 2008). Может ли это быть сделано более эффективно, чем это?

CREATE PROCEDURE GetNextUnprocessedRecord
AS
BEGIN
    SET NOCOUNT ON;

    --ID of record we want to select back
    DECLARE @iID BIGINT     

    -- Find the next processable record, and mark it as dispatched
    -- Must be done in a transaction to ensure no other query can get
    -- this record between the read and update
    BEGIN TRAN

        SELECT TOP 1
            @iID = [ID]
        FROM
            --Don't read locked records, only lock the specific record
            [MyRecords] WITH (READPAST, ROWLOCK)
        WHERE
            [Dispatched] is null
        ORDER BY
            [Received]

        --Mark record as picked up for processing   
        UPDATE 
            [MyRecords]
        SET
            [Dispatched] = GETDATE()
        WHERE
            [ID] = @iID     

    COMMIT TRAN

    --Select back the specific record
    SELECT 
        [ID],
        [Data]
    FROM    
        [MyRecords] WITH (NOLOCK, READPAST)
    WHERE
        [ID] = @iID

END

Ответы [ 3 ]

3 голосов
/ 22 февраля 2009

Использование подсказки блокировки READPAST правильное, и ваш SQL выглядит нормально.

Я бы добавил использование XLOCK, хотя это также HOLDLOCK / SERIALIZABLE

...
[MyRecords] WITH (READPAST, ROWLOCK, XLOCK)
...

Это означает, что вы получаете идентификатор и блокируете эту строку, пока вы продолжаете, и обновляете ее.

Редактировать: добавить индекс для столбцов Отправлено и Получено, чтобы ускорить его. Если [ID] (я предполагаю, что это PK) не кластеризован, ВКЛЮЧИТЕ [ID]. И фильтруйте индекс тоже, потому что это SQL 2008

Вы также можете использовать эту конструкцию, которая делает все это за один раз без XLOCK или HOLDLOCK

UPDATE
    MyRecords
SET
    --record the row ID
    @id = [ID],
    --flag doing stuff
    [Dispatched] = GETDATE()
WHERE
    [ID] = (SELECT TOP 1 [ID] FROM MyRecords WITH (ROWLOCK, READPAST) WHERE Dispatched IS NULL ORDER BY Received)

ОБНОВЛЕНИЕ, назначить, установить в одно

0 голосов
/ 22 февраля 2009

Вы можете хранить MyRecords в таблице «MEMORY» для ускорения обработки.

0 голосов
/ 22 февраля 2009

Вы можете назначить каждому процессу выбора уникальный идентификатор, а также добавить столбцы pickerproc и pickstate в свои записи. Тогда

ОБНОВЛЕНИЕ MyRecords
SET pickerproc = myproc,
pickstate = 'I' - для 'Я обрабатываю
WHERE Id = (ВЫБЕРИТЕ МАКС. (Id) ИЗ MyRecords WHERE pickstate = 'A') - 'A'vailable

Это позволит вам сделать запись за один атомный шаг, а остальную часть обработки вы сможете сделать на досуге. Затем вы можете установить для pickstate значение 'C'omplete', 'E'rror или любое другое, когда оно разрешится.

Я думаю, что Митч имеет в виду другую хорошую технику, в которой вы создаете таблицу очереди сообщений и вставляете туда идентификаторы. Существует несколько потоков SO - поиск по «таблице очередей сообщений».

...