Является ли эта хранимая процедура потокобезопасной? (или любой другой эквивалент на SQL Server)

С помощью других пользователей SO я сегодня утром создал пару таблиц и хранимых процедур, так как я далек от программиста БД.

Не мог бы кто-нибудь взглянуть на это и сказать мне, является ли это потокобезопасным? Я предполагаю, что это, вероятно, не тот термин, который используют администраторы баз данных / разработчики баз данных, но я надеюсь, что вы уловили идею: в основном, что произойдет, если этот sp выполняется, а другой появляется одновременно? Может ли одно мешать другому? Это вообще проблема SQL / SP?

CREATE PROCEDURE [dbo].[usp_NewTicketNumber]
    @ticketNumber int OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    INSERT INTO [TEST_Db42].[dbo].[TicketNumber]
               ([CreatedDateTime], [CreatedBy])
         VALUES
                (GETDATE(), SUSER_SNAME())
    SELECT @ticketNumber = IDENT_CURRENT('[dbo].[TicketNumber]');
    RETURN 0;
END

person serialhobbyist    schedule 27.09.2009    source источник
comment
Я полностью согласен с ответом gbn, но хотел бы добавить, что вы можете легко понять это самостоятельно - вы можете просто запустить свою хранимую процедуру одновременно из двух или более соединений в цикле много раз (›1 млн) и убедиться в этом сами.   -  person A-K    schedule 28.09.2009


Ответы (5)


Вероятно, вы не хотите использовать IDENT_CURRENT - это возвращает последний идентификатор, сгенерированный для рассматриваемой таблицы, в любом сеансе и любой области. Если кто-то другой вставит вставку не в то время, вы вместо этого получите его идентификатор!

Если вы хотите получить идентификатор, сгенерированный только что выполненной вставкой, лучше всего использовать OUTPUT для его получения. Раньше для этого обычно использовалась SCOPE_IDENTITY (), но есть проблемы с этим при параллельных планах выполнения.

Основной эквивалент безопасности потоков в SQL - это выполнение нескольких операторов, вызывающих неожиданное или нежелательное поведение. Два основных типа такого поведения, о которых я могу думать, - это блокировка (в частности, взаимоблокировки) и проблемы параллелизма.

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

Однако простая вставка, подобная той, что у вас, не должна вызывать блокировки, если только не задействовано что-то еще (например, транзакции базы данных).

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

person David Hall    schedule 27.09.2009
comment
-1 за рекомендацию SCOPE_IDENTITY; support.microsoft.com/kb/2019779. Вы все должны использовать предложение OUTPUT во всех случаях. - person Michael J. Gray; 13.02.2014
comment
@ MichaelJ: Серое спасибо, я это исправил. Раньше не слышал о проблеме. Вы случайно не знаете, когда это было обнаружено? - person David Hall; 14.02.2014
comment
@DavidHall В выпуске SQL Server 2005 они представили OUTPUT. Я отзову свой голос против и дам свой голос :) - person Michael J. Gray; 15.02.2014

Самый безопасный способ пойти сюда, вероятно, - использовать предложение Output, поскольку при определенных обстоятельствах (многопараллельная обработка) есть известная ошибка в scope_idendity.

CREATE PROCEDURE [dbo].[usp_NewTicketNumber]
AS
BEGIN
  DECLARE @NewID INT

  BEGIN TRANSACTION
  BEGIN TRY
    declare @ttIdTable TABLE (ID INT)
    INSERT INTO 
      [dbo].[TicketNumber]([CreatedDateTime], [CreatedBy])
    output inserted.id into @ttIdTable(ID)
    VALUES
      (GETDATE(), SUSER_SNAME())

    SET @NewID = (SELECT id FROM @ttIdTable)

    COMMIT TRANSACTION
  END TRY
  BEGIN CATCH
    ROLLBACK TRANSACTION
    SET @NewID = -1
  END CATCH

  RETURN @NewID
END

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

person druzin    schedule 07.09.2012
comment
Ссылка на MSKB scope_identity: support.microsoft.com/default.aspx ? scid = kb; en-US; 2019779 - person Mark Sowul; 07.08.2013

CREATE PROCEDURE [dbo].[usp_NewTicketNumber]
    @NewID int OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION
        INSERT INTO 
            [dbo].[TicketNumber] ([CreatedDateTime], [CreatedBy])
        VALUES
            (GETDATE(), SUSER_SNAME())

        SET @NewID = SCOPE_IDENTITY()

        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF XACT_STATE() <> 0
            ROLLBACK TRANSACTION;
        SET @NewID = NULL;
    END CATCH
END

Я бы не использовал RETURN для значимого использования данных: ни набора записей, ни выходного параметра. RETURN обычно используется для состояний ошибки (как в большинстве случаев хранящиеся в системе процессы):

EXEC @rtn = EXEC dbo.uspFoo
IF @rtn <> 0
    --do error stuff

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

Это «потокобезопасный», то есть его можно запускать одновременно.

person gbn    schedule 27.09.2009

Во-первых, почему бы вам все время просто не возвращать новый номер билета вместо 0? Какая-то конкретная причина для этого?

Во-вторых, чтобы быть абсолютно уверенным, вы должны заключить свои инструкции INSERT и SELECT в TRANSACTION, чтобы ничто извне не могло вмешаться.

В-третьих, с SQL Server 2005 и новее я бы заключил свои операторы в блок TRY .... CATCH и откатил транзакцию, если она не удалась.

Затем я бы попытался по возможности избегать указания сервера базы данных (TestDB42) в моих процедурах - что, если вы хотите развернуть этот процесс на новом сервере (TestDB43) ??

И, наконец, я бы никогда не использовал SET NOCOUNT в хранимой процедуре - это может привести к тому, что вызывающий абонент ошибочно решит, что сохраненная процедура завершилась неудачно (см. Мой комментарий к gbn ниже - это потенциальная проблема, если вы используете ADO.NET SqlDataAdapter только объекты; см. документы MSDN о том, как изменить ADO.NET data с помощью SqlDataAdapter для получения дополнительных объяснений).

Итак, мое предложение для вашей хранимой процедуры было бы следующим:

CREATE PROCEDURE [dbo].[usp_NewTicketNumber]
AS
BEGIN
  DECLARE @NewID INT

  BEGIN TRANSACTION
  BEGIN TRY
    INSERT INTO 
      [dbo].[TicketNumber]([CreatedDateTime], [CreatedBy])
    VALUES
      (GETDATE(), SUSER_SNAME())

    SET @NewID = SCOPE_IDENTITY()

    COMMIT TRANSACTION
  END TRY
  BEGIN CATCH
    ROLLBACK TRANSACTION
    SET @NewID = -1
  END CATCH

  RETURN @NewID
END

Марк

person marc_s    schedule 27.09.2009
comment
Вы бы никогда не использовали SET NOCOUNT ON? Действительно? - person gbn; 27.09.2009
comment
Что ж, если вы используете ADO.NET DataAdapter, использование SET NOCOUNT ON может привести к тому, что DataAdapter ошибочно будет считать, что сохраненная процедура завершилась неудачно, поскольку он интерпретирует возвращаемое значение как количество затронутых строк - и если оно равно 0 (в случае SET NOCOUNT ON), то DataAdapter интерпретирует это как сбой сохраненной процедуры. - person marc_s; 27.09.2009
comment
Этой статье 8 лет, поэтому, возможно, она относится к сети v1.0 dot net. Я не видел этой ошибки в течение многих лет: я предполагаю, что она уже исправлена ​​... - person gbn; 27.09.2009
comment
gbn: похоже, это поведение все еще применяется - это из документов .NET 3.5: msdn.microsoft.com/en-us/library/59x02y99.aspx - person marc_s; 27.09.2009
comment
Microsoft специально рекомендует не использовать SET NOCOUNT ON в сохраненных процессах, если вы используете их с ADO.NET SqlDataAdapters. - person marc_s; 27.09.2009
comment
Я поддерживаю некоторые SP, которые используются классическими ADO (COM / C ++) и ADO.NET SqlDataAdapters, и этот конфликт вызывает у меня потенциальные проблемы - есть ли у кого-нибудь еще эта проблема, и они ее уже решили? - person andywebsdale; 23.10.2011
comment
-1 за рекомендацию SCOPE_IDENTITY; support.microsoft.com/kb/2019779. На этой неделе мою команду это мучило, поэтому теперь мы используем OUTPUT во всех случаях. Рекомендую, Microsoft рекомендует. Я никогда не думал, что это когда-либо будет иметь значение. - person Michael J. Gray; 13.02.2014

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

У нас здесь был разработчик, который его использовал. Вставка из клиентского приложения произошла в то же время, когда база данных импортировала миллионы записей посредством автоматического импорта. Возвращенный ему идентификатор был из одной из записей, импортированных моим процессом. Он использовал этот идентификатор для создания записей для некоторых дочерних таблиц, которые теперь были прикреплены не к той записи. Хуже того, сейчас мы не знаем, сколько раз это происходило, прежде чем кто-то не мог найти информацию, которая должна была быть в дочерних таблицах (его изменение производилось в течение нескольких месяцев). Мало того, что мой автоматический импорт мог повлиять на его код, но другой пользователь, вставивший запись во время smae, мог сделать то же самое. Ident_current никогда не следует использовать для возврата идентификатора только что вставленной записи, поскольку он не ограничен процессом, который ее вызывает.

person HLGEM    schedule 28.09.2009
comment
Это именно то, чего я пытался избежать, поэтому большое спасибо за добавление. - person serialhobbyist; 01.10.2009