Сбой при работе из хранимой процедуры при вызове из задания агента SQL - PullRequest
0 голосов
/ 31 августа 2018

У меня есть хранимая процедура, которая прекрасно работает без ошибок в SQL Server Management Studio. Однако, когда та же хранимая процедура выполняется как шаг задания агента SQL, она заканчивается на:

Ошибка 3930: Текущая транзакция не может быть зафиксирована и не может поддерживать операции записи в файл журнала. Откат транзакции.

Хранимая процедура находится в схеме с именем [Billing]. Большинство используемых таблиц также находятся в схеме [Billing].

Основная хранимая процедура начинает транзакцию базы данных. Хранимые процедуры, вызываемые основной хранимой процедурой, выполняют всю свою работу с этой унаследованной транзакцией. Основная хранимая процедура отвечает за принятие или откат транзакции.

Пользователь базы данных, выполняющий шаг задания агента SQL, не имеет роли Sysadmin и dbo. Он принадлежит ролям базы данных db_datareader и db_datawriter, и ему даны разрешения Удалить, Выполнить, Вставить, Ссылки, Выбрать и обновить в схеме [Billing].

Вот основная хранимая процедура:

CREATE PROCEDURE [Billing].[GenerateBillXml]
  @pUseProductionPsSystem bit
  ,@pPeriodYear int
  ,@pPeriodMonthNumber int
  ,@pDocumentTypeName varchar(20)
  ,@pUser varchar(100)
  ,@pFilePath varchar(500)
  ,@pExportDateTime datetime2(7)
  ,@pResultCode int = 0 OUTPUT
AS
BEGIN
    SET NOCOUNT ON;

  SET @pResultCode = 0;

  DECLARE @transactionBegun bit = 0
          ,@RC int = 0
          ,@processDateTimeUtc datetime2(7) = GETUTCDATE()
          ,@billGenerationId int
          ,@PsJobSuffix char(2)
          ,@periodDate date
          ,@periodDateString char(6)
          ,@PsBaseJobNumber char(6)
          ,@okayToRun int = 0
          ,@msg varchar(500);

  BEGIN TRY
    /* calculate the period date */
    SET @periodDate = CONVERT(date,CAST(@pPeriodMonthNumber as varchar) + '/01/' + CAST(@pPeriodYear as varchar))
    SET @periodDateString = CONVERT(varchar, @periodDate, 12); -- yyMMdd

    /* retrieve the job suffix */
    SELECT @PsJobSuffix = CAST([PsJobSuffix] as char(2))
      FROM [dbo].[DocumentType] dt
      WHERE [Name] like @pDocumentTypeName;

    /* build the base job number */
    SET @PsBaseJobNumber = LEFT(@periodDateString, 4) + @PsJobSuffix

    /*
     * We've made it past the input check - record the fact that we're generating a bill
     */
    INSERT [Billing].[BillGeneration] (
      [PsBaseJobNumber], [Status], [RunBy], [ProcessDateTimeUtc]
    ) VALUES (
      @PsBaseJobNumber, 'Running', @pUser, @processDateTimeUtc
    );

    IF @@ROWCOUNT = 1
      SET @billGenerationId = SCOPE_IDENTITY();

    EXECUTE @RC = [Billing].[_0_OkayToGenerateBill]
      @PsBaseJobNumber
      ,@okayToRun OUTPUT
      ,@pResultCode OUTPUT;

    IF @pResultCode = 0  
    BEGIN
      -- called stored procedure completed without error
      IF @okayToRun = -1 -- this bill has already been generated
      BEGIN
        SET @msg = 'The billing for job ' + CAST(@PsBaseJobNumber as varchar) + ' has already been produced.';
        RAISERROR(@msg, 16, 1)
      END

      IF @okayToRun = -2 -- too early to run billing for this period
      BEGIN
        SET @msg = 'It is too early to generate billing for job ' + CAST(@PsBaseJobNumber as varchar) + '.';
        RAISERROR(@msg, 16, 1)
      END

      IF @okayToRun <> 1 -- unknown error...
      BEGIN
        SET @msg = 'Unknown error occured while determining whether okay to generate bill for job ' + CAST(@PsBaseJobNumber as varchar) + '.';
        RAISERROR(@msg, 16, 1)
      END
    END
    ELSE
    BEGIN
      SET @msg = 'Unknown failure in sub-stored procedure [Billing].[_0_OkayToRun]() for job ' + CAST(@PsBaseJobNumber as varchar) + '.';
      RAISERROR(@msg, 16, 1)  -- will cause branch to CATCH
    END

    /* Okay to generate bill */

    /* If not in a transaction, begin one */
    IF @@TRANCOUNT = 0
    BEGIN
      BEGIN TRANSACTION
      SET @transactionBegun = 1;
    END

    EXECUTE @RC = [Billing].[_1_GeneratePsPreBillData] 
       @PsBaseJobNumber
      ,@pUser
      ,@pResultCode OUTPUT;

    IF @pResultCode = 0
    BEGIN
      -- stored proced ran to successful completion
      EXECUTE @RC = [Billing].[_2_GetBillingDataForXmlGeneration]
         @pUseProductionPsSystem
        ,@PsBaseJobNumber
        ,@pResultCode OUTPUT;

      IF @pResultCode = 0
      BEGIN
        -- stored proced ran to successful completion
        IF @transactionBegun = 1
          -- all table data has been created/updated
          COMMIT TRANSACTION

        -- Output XML bill to file
        EXECUTE @RC = [Billing].[_3_GenerateBillingXmlFilesForPsJob] 
           @PsBaseJobNumber
          ,@pFilePath
          ,@pExportDateTime
          ,@pResultCode OUTPUT;

        IF @pResultCode <> 0
        BEGIN
          -- called stored procedure failed
          SET @msg = '[Billing].[_3_GenerateBillingXmlFilesForPsJob]() failed for job ' + CAST(@PsBaseJobNumber as varchar);
          RAISERROR(@msg, 16, 1)  -- will cause branch to CATCH
        END
      END
      ELSE
      BEGIN
        -- called stored procedure failed
        SET @msg = '[Billing].[_2_GetBillingDataForXmlGeneration]() failed for job ' + CAST(@PsBaseJobNumber as varchar);
        RAISERROR(@msg, 16, 1)  -- will cause branch to CATCH
      END
    END
    ELSE
    BEGIN
        -- called stored procedure failed
      SET @msg = '[Billing].[_1_GeneratePsPreBillData]() failed for job ' + CAST(@PsBaseJobNumber as varchar);
      RAISERROR(@msg, 16, 1)  -- will cause branch to CATCH
    END

    -- bill generation was successful
    IF @billGenerationId IS NOT NULL
      UPDATE [Billing].[BillGeneration]
        SET [Status] = 'Successful', [ProcessEndDateTimeUtc] = GETUTCDATE()
        WHERE [Id] = @billGenerationId;

  END TRY
  BEGIN CATCH
    -- rollback transaction if we started one
    IF @transactionBegun = 1
      ROLLBACK TRANSACTION

    -- record the error
    INSERT [Billing].[BillGenerationError] (
      [DateTime], [Object], [ErrorNumber], [ErrorMessage]
    ) VALUES (
      GETDATE(), OBJECT_NAME(@@PROCID), ERROR_NUMBER(), ERROR_MESSAGE()
    );

    -- bill generation failed
    IF @billGenerationId IS NOT NULL
      UPDATE [Billing].[BillGeneration]
        SET [Status] = 'Failed'
            ,[Note] = ERROR_MESSAGE()
            ,[ProcessEndDateTimeUtc] = GETUTCDATE()
        WHERE [Id] = @billGenerationId;

    SELECT ERROR_NUMBER() as ErrorNumber;
    SELECT ERROR_MESSAGE() as ErrorMessage;
    SET @pResultCode = 1
  END CATCH
END

1 Ответ

0 голосов
/ 06 сентября 2018

Как @Lukasz Szozda намекнул в одном из своих комментариев на мой вопрос, проблема заключалась в том, что когда задание агента SQL выполняло команду BCP.EXE, оно выполнялось под учетной записью службы, используемой агентом SQL, который для меня является довольно ограниченный «Локальный системный» аккаунт. В этот момент для меня стало очевидным, что необходимо использовать учетную запись Proxy. Поэтому я создал прокси под Operating System (CmdExec), который был единственным выбором, который имел смысл.

Я вернулся к шагу задания, чтобы изменить его на использование прокси, но затем заметил, что в его текущем типе Transact-SQL script (TSQL) нет способа назначить учетную запись прокси.

Попробовав несколько вещей, наконец решил поместить операторы TSQL, находящиеся на шаге задания, в новую хранимую процедуру, а затем вызвать эту хранимую процедуру из исполняемого файла командной строки SQL SQLCMD.EXE. Затем я изменил тип шага задания с Transact-SQL script (TSQL) на Operating System (CmdExec). Затем я мог бы установить поле Run As для прокси, который я создал ранее. Я указал команду для запуска как CMD.EXE /c SQLCMD.EXE -S [ServerName] -Q "EXEC [NewProcedureName] [parameters]".

Если вам интересно, почему я запускаю SQLCMD.EXE в CMD.EXE, это потому, что одним из параметров новой хранимой процедуры была текущая дата в определенном формате ('%date:~4,10%'), которую Среда выполнения заданий SQL Server не поддерживает, но, безусловно, CMD.EXE.

В целом, я думаю, что это заняло немного больше усилий, чем я ожидал.

...