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

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

Может кто-нибудь задуматься над этим и сказать мне, если он потокобезопасен? Я предполагаю, что, вероятно, не используется термин DBAs/DB-разработчики, но я надеюсь, что вы получите идею: в основном, что происходит, если этот 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

Ответ 1

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

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

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

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

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

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

Ответ 2

Самый безопасный способ перейти сюда, вероятно, будет заключаться в использовании предложения 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

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

Ответ 3

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 обычно используется для состояний ошибок (например, системные хранимые procs в большинстве случаев):

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

Вы также можете использовать предложение OUTPUT для возврата набора записей.

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

Ответ 4

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

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

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

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

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

Итак, мое предложение для сохраненного proc будет:

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

Марк

Ответ 5

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

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