ОБНОВЛЕНИЕ из таблицы в собственной хранимой процедуре SQL (Hekaton)

Я переношу очередь на диске в память SQL Server 2016, чтобы реализовать очередь.

Это мой формат очереди:

CREATE TABLE dbo.SimpleQueue
(
   MsgId BIGINT NOT NULL PRIMARY KEY NONCLUSTERED IDENTITY(1, 1),
   Payload VARCHAR(7500) NOT NULL,
   IsDeleted BIT NOT NULL
) WITH (MEMORY_OPTIMIZED=ON)
GO

Это моя Enqueue родная хранимая процедура SQL Server:

CREATE PROCEDURE dbo.Enqueue(@Payload VARCHAR(7500), @IsDeleted BIT)
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS BEGIN ATOMIC WITH
(TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = 'english')

  INSERT INTO dbo.SimpleQueue (Payload, IsDeleted) VALUES (@Payload, @IsDeleted); 

END
GO

Я пытаюсь записать собственную хранимую процедуру Dequeue SQL Server, но у меня возникают некоторые трудности с тем, как реализовать UPDATE с использованием результатов SELECT или таблицы переменных.

До сих пор я пробовал:

CREATE PROCEDURE dbo.Dequeue(@BatchSize INT = 1)
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS BEGIN ATOMIC WITH
( TRANSACTION ISOLATION LEVEL = SNAPSHOT,LANGUAGE = 'english' )
   UPDATE dbo.SimpleQueue
        SET IsDeleted=1
        WHERE MsgId = (
            SELECT TOP(@BatchSize) MsgId, Payload
                FROM dbo.SimpleQueue
                WHERE IsDeleted = 0)
END
GO

Но я получаю эту ошибку:

Подзапросы (запросы, вложенные в другой запрос) поддерживаются только в инструкциях SELECT с модулями, скомпилированными в собственном коде.

Поэтому я попробовал другой подход, используя переменную для хранения результата.

Сначала я создал тип таблицы:

CREATE TYPE dbo.SimpleDequeue
  AS TABLE 
   (
    MsgId BIGINT NOT NULL PRIMARY KEY NONCLUSTERED, 
    Payload INT NOT NULL
   )
   WITH (MEMORY_OPTIMIZED=ON)
GO

Пока все хорошо, затем я попытался использовать его:

CREATE PROCEDURE dbo.Dequeue(@BatchSize INT = 1)
WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER
AS BEGIN ATOMIC WITH
( TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = 'english')

    DECLARE @result dbo.SimpleDequeue;

    INSERT @result 
        SELECT TOP(@BatchSize) MsgId, Payload FROM dbo.SimpleQueue
        WHERE IsDeleted = 0

    UPDATE dbo.SimpleQueue 
        SET IsDeleted = 1 
        WHERE 
            @result.MsgId = dbo.SimpleQueue.MsgId

    SELECT MsgId, Payload FROM @result
END
GO

Я получаю эту ошибку:

Необходимо объявить скалярную переменную "@result".

(только при использовании @result на WHERE @result.MsgId = dbo.SimpleQueue.MsgId)

Вот старый процесс удаления из очереди с использованием дисковых таблиц SQL Server:

CREATE PROCEDURE dbo.DequeueInDisk
    @BatchSize INT = 1
AS
BEGIN
    SET NOCOUNT ON;
    WITH 
    cte AS (
        SELECT TOP(@BatchSize) Payload
        FROM dbo.SimpleQueue WITH (ROWLOCK, READPAST)
        ORDER BY MsgId
    )
    DELETE FROM cte OUTPUT deleted.Payload;
END

Как я могу сделать это UPDATE и OUTPUT обновленными значениями (с высокой производительностью, так как это критично)?


person João Antunes    schedule 10.09.2015    source источник


Ответы (2)


Я думаю, что ваш подход имеет смысл с точки зрения разработки SQL - вы должны думать наборами, а не последовательно. Но похоже, что Microsoft считает, что вам нужен другой подход для собственных скомпилированных процедур, более императивных и действительно построчных (см. Реализация UPDATE с FROM или подзапросами или Реализация функциональности MERGE в хранимой процедуре, скомпилированной в собственном коде. Таким образом, ваша процедура может выглядеть следующим образом:

create or alter procedure [dbo].[Dequeue](@BatchSize int = 1)
with native_compilation, schemabinding, execute as owner
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = 'english')
    declare
        @result dbo.SimpleDequeue;

    declare
        @MsgId int,
        @Payload varchar(7500),
        @i int = 0;

    while @i < @BatchSize
    begin
        select top (1)
            @MsgId = s.MsgId,
            @Payload = s.Payload
        from dbo.SimpleQueue as s
        where
            s.IsDeleted = 0
        order by
            s.MsgId;

        if @@rowcount = 0
        begin
            set @i = @BatchSize;
        end
        else
        begin
            update dbo.SimpleQueue set IsDeleted = 1 where MsgId = @MsgId;

            insert into @result (MsgId, Payload)
            select @MsgId, @Payload;

            set @i += 1;
        end;
    end;

    select MsgId, Payload from @result;
END

Я не проверял, насколько быстро это будет работать, но я определенно проверю это с некоторыми реальными числами, потому что у нас есть пара таких таблиц-очередей, и мне интересно, сможем ли мы немного повысить производительность с помощью Hekaton.

person Roman Pekar    schedule 08.07.2018

В вашей старой подпрограмме вы используете TOP(@BatchSize) с ORDER BY MsgId. Новый подход, кажется, не имеет этого ORDER BY... Вы получите случайный результат...

Ваш

WHERE MsgId = (
            SELECT TOP(@BatchSize) MsgId, Payload
                FROM dbo.SimpleQueue
                WHERE IsDeleted = 0
                /*added this!*/ ORDER BY MsgId  )

вернется с двумя столбцами и, возможно, с несколькими строками. Вы не можете сравнить это с "=".

То, что вы можете попробовать:

WHERE MsgId IN (
            SELECT TOP(@BatchSize) MsgId
                FROM dbo.SimpleQueue
                WHERE IsDeleted = 0
                ORDER BY MsgId)

Или вы можете попробовать использовать INNER JOIN, например:

 UPDATE dbo.SimpleQueue
        SET IsDeleted=1
   FROM dbo.SimpleQeueu
   INNER JOIN dbo.SimpleQueue AS sq ON dbo.SimpleQeueu.MsgId=sq.MsgId
                                       AND sq.IsDeleted=0
                                       --this is missing the TOP-clause

Что еще: вы можете попробовать INNER JOIN (SELECT TOP ... ) AS InnerSimpleQueue ON .. или, может быть, CROSS APPLY.

РЕДАКТИРОВАТЬ: еще один подход с CTE:

WITH myCTE AS
(
    SELECT TOP(@BatchSize) MsgId
    FROM dbo.SimpleQueue
    WHERE IsDeleted = 0
    ORDER BY MsgId
)   
UPDATE dbo.SimpleQueue
        SET IsDeleted=1
FROM dbo.SimpleQeueu
INNER JOIN myCTE ON myCTE.MsgId=dbo.SimpleQueue.MsgId
person Shnugo    schedule 10.09.2015
comment
Что касается ORDER BY, я намеренно опустил этот пункт. В соответствии с MSDN строки могут быть получены в соответствии с порядком, указанным при создании индекса. Если порядок сортировки индекса соответствует порядку сортировки, требуемому для конкретного запроса, например, если ключ индекса соответствует предложению ORDER BY, нет необходимости сортировать строки как часть выполнения запроса. Источник: msdn.microsoft.com/en-us/library/dn133166.aspx< /а> - person João Antunes; 10.09.2015
comment
Я пытался использовать UPDATE dbo.SimpleQueue SET IsDeleted = 1 WHERE dbo.SimpleQueue.MsgId IN (@result), но все равно получаю эту ошибку: необходимо объявить скалярную переменную @result. Не забывайте, что вы не можете использовать подзапросы в собственных хранимых процедурах. - person João Antunes; 10.09.2015
comment
@JoãoAntunes, ну, у меня нет опыта работы с таблицами в памяти (надо посмотреть!), но это больше похоже на объяснение того, как работает оптимизатор, поэтому нет необходимости в двойном ORDER BY, если сортировка подходит уже. В общем, нет абсолютно НЕТ гарантии, в каком порядке вы получите свой результат, если вы не укажете ORDER BY в самом внешнем запросе (то же самое с подзапросами...) - person Shnugo; 10.09.2015
comment
@JoãoAntunes, к сожалению, вы не можете использовать предложение IN с параметром ... Но, по крайней мере, в моем случае, вы можете использовать предложение IN с подзапросом. Ваш WITH NATIVE_COMPLIATION должен сделать это возможным... - person Shnugo; 10.09.2015
comment
Я добавлю ORDER BY просто для предосторожности :), так как он будет проигнорирован. Что касается предложения IN с использованием SELECTS, я не могу его использовать, потому что WHERE будет иметь подзапрос и выдаст эту ошибку: Подзапросы (запросы, вложенные в другой запрос) поддерживаются только в операторах SELECT с скомпилированными в собственном коде модулями. - person João Antunes; 10.09.2015
comment
Вы пробовали ВНУТРЕННЕЕ СОЕДИНЕНИЕ? Вы можете присоединиться к своему @result, так как он ведет себя как таблица. INNER JOIN гарантирует, что обрабатываются только подходящие строки... - person Shnugo; 10.09.2015
comment
Я пытался, но выдает ту же ошибку, потому что я не могу использовать @result :( Необходимо объявить скалярную переменную @result. Я думаю, что решение состоит в том, чтобы создать 2 встроенные хранимые процедуры и попытаться применить IN к результат хранимой процедуры, но я не уверен, как это делается и возможно ли это, собираюсь изучить этот подход. - person João Antunes; 10.09.2015
comment
@JoãoAntunes, только что отредактировал свой ответ. Посмотрите прямо на дно. Может быть, вы можете обмануть это с помощью CTE ... - person Shnugo; 10.09.2015
comment
@JoãoAntunes, IN с результатом SP нет. Но вы можете попробовать функцию, определяемую пользователем, или VIEW... - person Shnugo; 10.09.2015
comment
В памяти SQL не позволяет CTE :( вместо этого я использую типы как таблицы - person João Antunes; 10.09.2015
comment
Еще одна идея: вы можете передать свою таблицу фильтров в качестве параметра. так и будет заявлено... - person Shnugo; 10.09.2015
comment
@JoãoAntunes, и еще одна идея: может быть достаточно добавить переменную результатов в список параметров. Ваше должно быть объявлено, должно быть решено таким образом... - person Shnugo; 10.09.2015
comment
Спасибо за ваши усилия, но я могу добавить параметр только для чтения :/ только для собственных хранимых процедур. Я думаю, что у меня есть возможное решение моей проблемы, то есть сделать только SELECT и UPDATE с одним и тем же предложением. Поскольку это работает над той же операцией ATOMIC, я работаю над своей собственной версией таблицы, и другие транзакции не могут изменить мои значения между SELECT и UPDATE, поэтому результат всегда будет одинаковым. После того, как я подтвержу, что поведение правильное, я обновлю ответ - person João Antunes; 10.09.2015
comment
Удачи! Я бы, вероятно, попытался использовать функцию с табличным значением, получив MsgID и вернув результат, который вы можете затем попытаться использовать INNER JOIN. Но, как было сказано ранее, я не знаком с этой концепцией in-memory... - person Shnugo; 10.09.2015