Эффективная транзакция, блокировка записи


5

У меня есть хранимая процедура, которая выбирает 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 
  0

Я не уверен, что это TSQL является транзакционно safe ... 22 фев. 092009-02-22 08:07:45

  0

попробуйте поставить WAITFOR DELAY '0: 2: 0' после SELECT и до UPDATE, запустите SP и выполните тот же SP из другого соединения ... 22 фев. 092009-02-22 08:17:24

  0

на самом деле, я ошибаюсь! HOLDLOCK имеет тот же эффект, что и REPEATABLEREAD в таблице MyRecords. 22 фев. 092009-02-22 08:22:27

  0

По умолчанию лучше быть безопасным. В противном случае существует распространенный концептуальный хаос на свободе. 22 фев. 092009-02-22 08:43:46

  0

Я все еще думаю, что вам нужно будет сделать транзакцию SERIALIZABLE, а не REPEATABLEREAD 22 фев. 092009-02-22 08:55:17

3

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

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

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

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

Редактировать: добавьте индекс в столбцы Отправленные и Полученные, чтобы сделать его быстрее. Если [ID] (я предполагаю, что это PK) не кластеризуется, INCLUDE [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) 

UPDATE, assign, set in one

  0

Замечательные советы! Я добавил подсказку XLOCK и создал некластеризованный индекс в Received and Dispatched и включил идентификатор с фильтром «Dispatched IS NULL». Это в дополнение к моему кластерному индексу ID. Я все еще пытаюсь выяснить, установлено ли назначение обновления в одном быстрее или нет. 23 фев. 092009-02-23 07:07:14

+1

Спасибо. Я бы больше беспокоился о целостности транзакций, чем о прямой производительности. Единственное утверждение устраняет необходимость в XLOCK. 23 фев. 092009-02-23 07:48:33


0

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

UPDATE MyRecords
SET pickerproc = MyProc,
pickstate = 'I' - для «процесса I'n
WHERE ID = (SELECT MAX (Id) ОТ MyRecords ГДЕ pickstate = 'А') - 'A'vailable

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

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

  0

, что определенно небезопасно! 22 фев. 092009-02-22 08:51:33

  0

И почему это было бы? Он не будет передавать один и тот же MAX (Id) с помощью pickstate 'A', пока вы не измените его. По крайней мере, это не было в моем опыте, основанном, вероятно, на 10^6 + экземплярах. 22 фев. 092009-02-22 08:55:38

  0

.. и он не будет эффективен 22 фев. 092009-02-22 08:57:49

  0

Я слушаю ... в чем ваш аргумент? Я всегда могу узнать больше о SQL ... 22 фев. 092009-02-22 09:00:29

  0

@le dorfier: чтобы сделать транзакционную последовательность последовательной, ее нужно будет обернуть в транзакцию SERIALIZABLE. В противном случае существует риск получения грязных считываний/не повторяющихся чтений/фантомных чтений/ 22 фев. 092009-02-22 09:01:27

  0

, если я не ошибаюсь, операция MAX будет проверять строки. Если это большой стол, он будет держать транснарт открытым в течение относительно длительного времени. 22 фев. 092009-02-22 09:03:12

  0

Мне не хотелось бы снова выяснять, что это еще раз, но я начинаю с предположения, что ACID применяется по умолчанию. Вы не согласны? 22 фев. 092009-02-22 09:03:40

  0

(При отражении) Я вижу вашу точку (т. Е. Низкую мощность столбца pickstate). Но он разрешен «фильтрованным индексом», доступным в SQL 2008. См. Http://blog.sqlauthority.com/2008/09/01/sql-server-2008-introduction-to-filtered-index-improve-performance-with -filtered-index/ 22 фев. 092009-02-22 09:18:22


0

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

  0

Таблица может стать довольно большой, плюс, что произойдет, если сервер сработает? 22 фев. 092009-02-22 19:27:06