Хобрук: Ваш путь к мастерству в программировании

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

У меня есть хранимая процедура SQL Server, вызываемая удаленно из стороннего приложения. Хранимая процедура выполняется правильно при локальном вызове, но не выполняется при удаленном вызове. Под локальным я подразумеваю, если я щелкну правой кнопкой мыши и выберу «Выполнить»; или если я ввожу exec sp_EBRRecipeMove из окна запроса. Несмотря на то, что он не выполняется при удаленном вызове, никаких ошибок не возвращается.

Предыстория: эта хранимая процедура является частью последовательности хранимых процедур, которые вызываются из клиентского API. API вызывает sp_1. sp_1 вызывает sp_2, sp3 и sp_4. Если ошибок нет, sp_1 вызывает sp_EBRRecipeMove. Все это делается для проверки ошибок и переноса заданных значений из одной базы данных в другую.

Я рассыпал RAISERROR по всему сценарию, чтобы понять, как далеко он заходит в сценарии. Журнал SQL показывает, что сценарий выполняется, но значения никогда не обновляются, а значения из исходных таблиц никогда не переносятся в целевые таблицы.

До сих пор приложение конечного пользователя (я полагаю, PHP) вызывает такое поведение, и простое приложение Java для вызова хранимой процедуры также вызывает его. Я могу выполнить это вручную/локально без проблем. У меня не будет доступа к PHP-коду.

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

Чтобы уменьшить вероятность проблемы с правами доступа к БД, я предоставил пользователю, который используется для удаленного подключения, доступ системного администратора.

Я проверил, что все экземпляры столбца RecipeID в базе данных и связанные с ними переменные имеют значение INT. В целевой таблице этот столбец имеет значение Int NOT NULL, но в других таблицах (EBR) я разрешаю значения Null, поскольку значение может быть не заполнено.

ОБНОВЛЕНИЕ: с тех пор я убедился, что стороннее приложение не вызывает хранимую процедуру через транзакцию. Из базы данных Oracle, которую они используют, это простой вызов.

Используя уже опубликованные подсказки, я значительно устранил неполадки, поскольку смог успешно выполнить хранимую процедуру, вызвав ее из стороннего Java-приложения. Однако при внесении изменений в настройку клиента команды не выполняются. Я прокомментировал код и попытался выполнить по одной команде за раз с помощью Try... Catch вокруг каждого. Попробуйте... Catch не возвращает никаких ошибок, но команды не выполняются. Есть ли в таблицах специальное разрешение или настройка, которые разрешают удаленное выполнение команд «Вставить», «Удалить» и «Обновить»? Любая помощь или подсказки очень ценятся!

ОБНОВЛЕНИЕ 2: из другого сообщения stackoverflow, связанного с Deletes от @Remus Rusanu, я могу предоставить следующую информацию о случаях, когда Deletes или другие команды могут не выполняться, и, надеюсь, завтра опубликую свой исправленный код:

• DELETE не выполняется (проблема управления логическим потоком, она использует другую ветвь IF...) [Это не так, у меня есть RAISERROR непосредственно перед и сразу после команды, которая записывает разумную информацию с учетом входных данных (за исключением того, что Удаление не произошло)]

• DELETE выполняется, но не влияет на какие-либо строки (предложение WHERE или проблема JOIN) [Это не так, или, по крайней мере, кажется, поскольку я могу взять Delete...Where и поместить его в запрос и выполнить. Я не использую соединения.]

• DELETE выполняется и ошибки, и ошибка молча подавляется (проблема обработки ошибок) [Это не должно быть проблемой, поскольку я закомментировал весь код, кроме 1 оператора Delete...Where и поместил Try...Catch непосредственно вокруг него ]

• DELETE выполняется, но эффект откатывается (проблема управления транзакциями) [Это не тот случай, потому что я не использую транзакции, и вызывающий клиент не использует транзакцию.]

• DELETE выполняется, но эффект компенсируется приложением (проблема с логикой приложения). Кроме того, клиент использует простую команду CALL от Oracle.]

• DELETE не выполняется, потому что предыдущая ошибка останавливает запрос (опять проблема с обработкой ошибок) [Этого не должно быть, потому что я вижу, что RAISERROR регистрирует разумную информацию непосредственно перед и сразу после оператора Delete.]

• DELETE выполняется, но ваша проверка неверна (PBCAK) [Это не так, поскольку строка не удаляется. Оператор Select таблицы указывает, что после выполнения хранимой процедуры строка не повреждена.]

ALTER PROCEDURE [dbo].[sp_EBRRecipeMove]
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    Declare @Version smallint;
    Declare @CheckedOutID tinyint;
    Declare @AbleToSave bit;
    Declare @EquipName varchar(80);
    Declare @RcpName varchar(80);
    Declare @EquipID tinyint;
    Declare @status int;
    Declare @prRcpName varchar(80);
    Declare @prVersion smallint;
    Declare @tmprows int;
    Declare @recipeid int;
    Declare @tmpid int
    Declare @groupid tinyint;
    Declare @rcpcomment varchar(200);
    Declare @prrcpcomment varchar(200);
    Declare @savedatim datetime;
    Declare @saveuser varchar(80);
    Declare @approvaldatim datetime;
    Declare @approvaluser varchar(80);
    Declare @deactdatim datetime;
    Declare @deactuser varchar(80);
    Declare @existingRecID int;
    DECLARE @ErrorSave int
        SET @ErrorSave = 0


    -- Select recipe info from EBRRecipeHeader table
    SELECT @RcpName=recipename
    FROM EBRRecipeHeader

    -- Select recipe info from prRecipeHeader table
    SELECT @prRcpName=RecipeName
    FROM prRecipeHeader
    WHERE RecipeName = @RcpName 


        set @Version = 1;       
        set @status = 3; 
        set @groupid = 1;
        set @savedatim = CURRENT_TIMESTAMP;
        set @saveuser = 'EBR'
        set @approvaldatim = CURRENT_TIMESTAMP;
        set @approvaluser = 'EBR'
        set @CheckedOutID = 0;     


        if @@ROWCOUNT = 0
            begin
                INSERT INTO dbo.prRecipeHeader(
                    GroupID,
                    Status,
                    RecipeName,
                    Version,
                    RecipeComment,
                    SaveDateTime,
                    SaveUser,
                    ApprovalDateTime,
                    ApprovalUser,
                    DeactivateDateTime,
                    DeactivateUser,
                    CheckedOutID
                    ) VALUES (
                    @groupid,
                    @status,
                    @RcpName,
                    @Version,
                    @rcpcomment,
                    @savedatim,
                    @saveuser,
                    @approvaldatim,
                    @approvaluser,
                    @deactdatim,
                    @deactuser,
                    @CheckedOutID        
                    )

                end 

        ELSE 
            begin

                DELETE FROM prRecipeHeader
                WHERE RecipeName = @RcpName 

                INSERT INTO dbo.prRecipeHeader(
                    GroupID,
                    Status,
                    RecipeName,
                    Version,
                    RecipeComment,
                    SaveDateTime,
                    SaveUser,
                    ApprovalDateTime,
                    ApprovalUser,
                    DeactivateDateTime,
                    DeactivateUser,
                    CheckedOutID
                    ) VALUES (
                    @groupid,
                    @status,
                    @RcpName,
                    @Version,
                    @rcpcomment,
                    @savedatim,
                    @saveuser,
                    @approvaldatim,
                    @approvaluser,
                    @deactdatim,
                    @deactuser,
                    @CheckedOutID        
                    )

            end --if <>0


    select @recipeid = RecipeID from prRecipeHeader where prRecipeHeader.RecipeName = @RcpName;


declare @recipeidText as varchar(8)
set @recipeidText = convert(varchar(8), @recipeid)
RAISERROR (@recipeidtext,10,1) WITH LOG     
RAISERROR ('RecipeID before Globals',10,1) WITH LOG  



    UPDATE  EBRRecipeGlobals    SET EBRRecipeGlobals.recipeid = @recipeid;

set @recipeidText = convert(varchar(8), @recipeid)
RAISERROR (@recipeidtext,10,1) WITH LOG     
RAISERROR ('RecipeID before global insert',10,1) WITH LOG  



    INSERT INTO prRecipeGlobals SELECT * FROM EBRRecipeGlobals where EBRRecipeGlobals.recipeid = @recipeid;
    DELETE From EBRRecipeGlobals Where EBRRecipeGlobals.recipeid = @recipeid;

set @recipeidText = convert(varchar(8), @recipeid)
RAISERROR (@recipeidtext,10,1) WITH LOG     
RAISERROR ('RecipeID after globals insert, before steps',10,1) WITH LOG  


    UPDATE EBRRecipeSteps SET EBRRecipeSteps.RecipeID = @recipeid;

set @recipeidText = convert(varchar(8), @recipeid)
RAISERROR (@recipeidtext,10,1) WITH LOG     
RAISERROR ('RecipeID before steps insert',10,1) WITH LOG  

    INSERT INTO prRecipeSteps SELECT * FROM EBRRecipeSteps where EBRRecipeSteps.RecipeID = @recipeid;
    DELETE From EBRRecipeSteps Where EBRRecipeSteps.RecipeID = @recipeid;



    DELETE FROM EBRRecipeHeader 

end

  • Клиент запускает это внутри транзакции из своего кода, который откатывается? Возможно, вы захотите запустить трассировку этого процесса и посмотреть, что происходит. В качестве примечания вам следует избегать префикса sp_. sqlperformance.com/2012/10/t-sql-queries/sp_prefix 19.01.2016
  • Используйте трассировку профилировщика, чтобы узнать, какие команды отправляет приложение. (или не отправлять). 19.01.2016
  • Примечание: вы не должны не использовать префикс sp_ для своих хранимых процедур. Microsoft зарезервировала этот префикс для собственного использования ( см. Именование хранимых процедур), и вы рискуете столкнуться с конфликтом имен в будущем. Это также плохо сказывается на производительности ваших хранимых процедур. Лучше просто избегать sp_ и использовать в качестве префикса что-то другое - или вообще не использовать префикс! 20.01.2016
  • Быстрые комментарии: @@ROWCOUNT = 0 работает не так, как вы ожидаете. Он всегда будет не равен 0 из-за SET, который вы делаете над ним. Вместо этого используйте @prRcpName is null. Кроме того, есть ли у prRecipeSteps внешний ключ для prRecipeHeader и каскадируется ли он, если вы удаляете из последнего, если да? Потому что в противном случае, учитывая вашу проблему @@rowcount, вы можете нормально протестировать SSMS, затем снова запустить тот же тест, и он потерпит неудачу, потому что вы не можете удалить из prRecipeHeader, не удалив сначала из prRecipeSteps. 20.01.2016
  • @SeanLange - я уточню у клиента, используют ли они транзакцию. В дальнейшем я не буду использовать префикс sp_. 20.01.2016
  • @TabAlleman Хотя я обновил свой SQL с Express до Enterprise, инструменты полной настройки не установились. Удаление и переустановка кажется нетривиальной задачей, но в будущем я обязательно выберу все инструменты настройки при установке. 20.01.2016
  • @JasonWhitish Это действительно отличный совет, и я дам вам знать, как он работает. Я собираюсь использовать Try...Catch, который вы упомянули ниже, чтобы увидеть, вызывает ли @@Rowcount ошибку. Спасибо за четкое объяснение использования Try...Catch. Я много читал об этом, но действительно не был уверен, что реализую. Я проверил в своей БД, если я удаляю строку из prRecipeHeader, строки в prRecipeSteps с соответствующим RecipeID также удаляются. Если я сначала удаляю из шагов, строка заголовка не удаляется. 20.01.2016
  • Приятно слышать о том, как взаимодействуют prRecipeHeader и prRecipeSteps. Это означает, что у них есть каскадный внешний ключ, так что если вы удалите из ссылочной таблицы (prRecipeHeader), он также будет удален из ссылочной таблицы. 20.01.2016
  • Я думаю, вы можете просто обновить существующую установку, а не удалять + переустанавливать. Это все еще не тривиально, но оно того стоит. Не представляю жизни без профилировщика. 20.01.2016
  • @JasonWhitish Я использовал Try Catch, указанный ниже, для устранения неполадок, и это ОГРОМНО помогло. Я выполнил 1 команду, закомментировав остальную часть кода; если это было успешно, я раскомментировал следующую команду и поместил вокруг нее Try Catch. @@ROWCOUNT вызывал проблемы. Вместо этого я использовал ваше предложение, чтобы попробовать IS NULL, и мне удалось выполнить хранимую процедуру из бесплатного инструмента Java под названием Execute Query по сети. Я считаю это успехом, потому что изначально он тоже не работал с этим инструментом. Я надеюсь, что сегодня смогу выполнить развертывание в системе заказчика. 21.01.2016

Ответы:


1

Я бы порекомендовал сделать две вещи, которые, надеюсь, сэкономят вам много времени.

Первый — попытаться зафиксировать статус возврата процесса, когда вы вызываете его из sp_1. Что-то вроде этого:

declare @i int, @e nvarchar(20);

EXEC @i = sp_EBRRecipeMove

SELECT @e = N'RETURN STATUS = ' + cast(@i as nvarchar(4));

RAISERROR(@e, 10, 1) WITH LOG;

Если вы посмотрите и увидите, что статус возврата отличается от 0, то процесс определенно где-то дает сбой.

Второе, что вы можете сделать, и вы можете сделать это одновременно с вышеизложенным, это добавить блок TRY..CATCH к вашему коду в sp_EBRRecipeMove. Прямо сейчас, поскольку у вас нет никакой обработки ошибок, процесс продолжает пытаться пройти через шаги, которые у него есть, независимо от того, насколько они успешны. Таким образом, становится трудно определить, где именно ваша проблема. TRY..CATCH перейдет к CATCH, как только у вас возникнет ошибка.

So:

BEGIN TRY
    --(rest of code from proc)
END TRY
BEGIN CATCH
    RAISERROR(ERROR_MESSAGE(), 10,1) WITH LOG
END CATCH 

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

19.01.2016

2

Используя SQL Profiler, Try...Catch, RAISERRORS и работая вместе с командой конечных пользователей, мы смогли решить следующие проблемы: API вызова хранимой процедуры не использовал транзакцию; однако, когда они закрывают соединение между Caller и базой данных SQL, в которой находится SP, мы получаем 2 сообщения x if @@TRANCOUNT > 0 ROLLBACK. При закрытии соединения команды откатывались.

Сначала, когда мы увидели @@TRANCOUNT > 0 ROLLBACK, мы исследовали неявные/явные транзакции и обнаружили, что лучше всего использовать транзакции в моем SP с Try...Catch, что мы и сделали, но обнаружили, что у нас все еще есть @ @TRANCOUNT > 0 ОТМЕНА.

Затем мы выделили хранимую процедуру, которая, по нашему мнению, была проблемой, и выполнили ее отдельно. Мы обнаружили, что начальная часть хранимой процедуры выполняется без отката или ошибок (что неудивительно). Затем, не закрывая соединение, мы вызвали вторую часть хранимой процедуры (в которой, как мы предполагали, была проблема), и она не откатилась и не выдала ошибку (это было удивительно). Система не выполняла откат до тех пор, пока соединение не было закрыто.

26.01.2016
Новые материалы

Основы принципов S.O.L.I.D, Javascript, Git и NoSQL
каковы принципы S.O.L.I.D? Принципы SOLID призваны помочь разработчикам создавать надежные, удобные в сопровождении приложения. мы видим пять ключевых принципов. Принципы SOLID были разработаны..

Как настроить Selenium в проекте Angular
Угловой | Селен Как настроить Selenium в проекте Angular Держите свое приложение Angular и тесты Selenium в одной рабочей области и запускайте их с помощью Mocha. В этой статье мы..

Аргументы прогрессивного улучшения почти всегда упускают суть
В наши дни в кругах веб-разработчиков много болтают о Progressive Enhancement — PE, но на самом деле почти все аргументы с обеих сторон упускают самую фундаментальную причину, по которой PE..

Введение в Джанго Фреймворк
Схема «работать умно, а не усердно» В этой и последующих статьях я познакомлю вас с тем, что такое фреймворк Django и как создать свое первое приложение с помощью простых и понятных шагов, а..

Настольный ПК как «одно кольцо, чтобы править всеми» домашних компьютеров
Вид после 9 месяцев использования С настольных компьютеров все началось, но в какой-то момент они стали «серверами», и мы все перешли на ноутбуки. В прошлом году я столкнулся с идеей настольных..

Расширенные методы безопасности для VueJS: реализация аутентификации без пароля
Руководство, которое поможет вам создавать безопасные приложения в долгосрочной перспективе Безопасность приложений часто упускается из виду в процессе разработки, потому что основная..

стройный-i18следующий
Представляем стройную оболочку для i18next. Эта библиотека, основанная на i18next, заключает экземпляр i18next в хранилище svelte и отслеживает события i18next, такие как languageChanged,..