Каковы лучшие практики в написании хранимой процедуры SQL - PullRequest
30 голосов
/ 20 ноября 2008

Я обнаружил, что хранимые процедуры SQL очень интересны и полезны. Я написал хранимые процедуры, но я хочу написать хорошо продуманные, хорошо настроенные и сжатые SP для любых требований, а также хотел бы узнать о любых хитростях и рекомендациях для хранимых процедур. Как перейти от начального к продвинутому этапу написания хранимых процедур?

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

Ответы [ 11 ]

40 голосов
/ 20 ноября 2008

Вот мои рекомендации по обработке ошибок хранимых процедур.

  • Для повышения производительности вызывайте каждую хранимую процедуру, используя ее полное имя: это имя сервера, имя базы данных, имя схемы (владельца) и имя процедуры.
  • В сценарии, который создает каждую хранимую процедуру, явно укажите, какие роли могут выполнять процедуру, например общедоступная или какая-либо другая.
  • Используйте sysmessage, sp_addmessage и заполнители вместо жестко закодированных сообщений об ошибках.
  • При использовании sp_addmessage и sysmessages всегда используйте номер сообщения об ошибке 50001 или выше.
  • При использовании RAISERROR всегда указывайте уровень серьезности <= 10 для предупреждающих сообщений. </li>
  • В RAISERROR всегда указывайте уровень серьезности для сообщений об ошибках от 11 до 16.
  • Помните, что использование RAISERROR не всегда прерывает выполнение какого-либо пакета, даже в контексте триггера.
  • Сохраните @@ error в локальной переменной перед ее использованием или запросом.
  • Сохраните @@ rowcount в локальной переменной перед его использованием или запросом.
  • Для хранимой процедуры используйте возвращаемое значение, чтобы указать только успех / неудачу, а не какую-либо другую / дополнительную информацию.
  • Возвращаемое значение для хранимой процедуры должно быть установлено на 0, чтобы указать успешность, и ненулевое, чтобы указать на неудачу.
  • Установить ANSI_WARNINGS ON - это обнаруживает нулевые значения в любом совокупном назначении и любом назначении, которое превышает максимальную длину символа или двоичного столбца.
  • Установить NOCOUNT ON по многим причинам.
  • Тщательно продумайте, хотите ли вы XACT_ABORT ON или OFF . В любом случае будьте последовательны.
  • Выход при первой ошибке - это реализует модель KISS.
  • При выполнении хранимой процедуры всегда проверяйте @@ error и возвращаемое значение. Например:

    EXEC @err = AnyStoredProc @value
    SET  @save_error = @@error
    -- NULLIF says that if @err is 0, this is the same as null
    -- COALESCE returns the first non-null value in its arguments
    SELECT @err = COALESCE( NULLIF(@err, 0), @save_error )
    IF @err <> 0 BEGIN 
        -- Because stored proc may have started a tran it didn't commit
        ROLLBACK TRANSACTION 
        RETURN @err 
    END
    
  • При выполнении локальной хранимой процедуры, которая приводит к ошибке, выполните откат, поскольку процедура может запустить транзакцию, которую она не зафиксировала, или откат.
  • Не думайте, что из-за того, что вы не начали транзакцию, нет активной транзакции - возможно, она была запущена вызывающим абонентом.
  • В идеале, избегайте отката транзакции, которая была запущена вашим абонентом - проверьте @@ trancount.
  • Но в триггере всегда выполняйте откат, поскольку вы не знаете, инициировал ли вызывающая сторона активную транзакцию (потому что @@ trancount всегда> = 1).
  • Всегда сохраняйте и проверяйте @@ error после следующих утверждений:

    INSERT, DELETE, UPDATE
    SELECT INTO
    Invocation of stored procedures
    invocation of dynamic SQL
    COMMIT TRANSACTION
    DECLARE and OPEN CURSOR
    FETCH from cursor
    WRITETEXT and UPDATETEXT
    
  • Если DECLARE CURSOR завершается неудачно с глобальным курсором процесса (по умолчанию), введите оператор для освобождения курсора.
  • Будьте осторожны с ошибкой в ​​UDF. Когда в UDF возникает ошибка, выполнение функции немедленно прерывается, как и запрос, вызвавший UDF, но @@ error равен 0! В этих обстоятельствах вы можете захотеть запустить с SET XACT_ABORT ON.
  • Если вы хотите использовать динамический SQL, попробуйте использовать только один SELECT в каждом пакете, потому что @@ error содержит только состояние последней выполненной команды. Наиболее вероятными ошибками в пакете динамического SQL являются синтаксические ошибки, и SET XACT_ABORT ON не учитывает их.
18 голосов
/ 20 ноября 2008

Единственный прием, который я всегда стараюсь использовать: всегда включать пример использования в комментарии в верхней части. Это также полезно для тестирования вашего SP. Мне нравится включать наиболее распространенные примеры - тогда вам даже не понадобится SQL-запрос или отдельный файл .sql с вашим любимым вызовом, так как он хранится прямо на сервере (это особенно полезно, если у вас есть хранимые процедуры, которые смотрят на sp_who выводит для блоков или чего-либо еще и принимает несколько параметров).

Что-то вроде:

/*
    Usage:
    EXEC usp_ThisProc @Param1 = 1, @Param2 = 2
*/

Затем, чтобы протестировать или запустить SP, вы просто выделяете этот раздел в вашем скрипте и выполняете.

11 голосов
/ 07 октября 2009
  1. Всегда использовать SET NOCOUNT ON
  2. Если вы собираетесь выполнить две или более вставки / обновления / удаления, используйте транзакцию.
  3. Никогда не называйте свои процы "sp_". SQL Server сначала ищет в базе данных master, не находит ее, а затем просматривает базу данных. Если вы по-разному называете свои процессы, SQL Server сначала будет искать в вашей базе данных.

Bad:

SET NOCOUNT ON
BEGIN TRAN
  INSERT...
  UPDATE...
COMMIT

Лучше, но выглядит неопрятно, и большая боль в коде:

SET NOCOUNT ON
BEGIN TRAN
  INSERT...
  IF @ErrorVar <> 0
  BEGIN
      RAISERROR(N'Message', 16, 1)
      GOTO QuitWithRollback
  END

  UPDATE...
  IF @ErrorVar <> 0
  BEGIN
      RAISERROR(N'Message', 16, 1)
      GOTO QuitWithRollback
  END

  EXECUTE @ReturnCode = some_proc @some_param = 123
  IF (@@ERROR <> 0 OR @ReturnCode <> 0)
       GOTO QuitWithRollback 
COMMIT
GOTO   EndSave              
QuitWithRollback:
    IF (@@TRANCOUNT > 0)
        ROLLBACK TRANSACTION 
EndSave:

Хорошо:

SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRY
    BEGIN TRAN
    INSERT...
    UPDATE...
    COMMIT
END TRY
BEGIN CATCH
    IF (XACT_STATE()) <> 0
        ROLLBACK
END CATCH

Лучший:

SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRAN
    INSERT...
    UPDATE...
COMMIT

Так где же обработка ошибок в «лучшем» решении? Вам не нужно ничего. См. SET XACT_ABORT ON , это означает, что выполнить автоматический откат в случае каких-либо ошибок. Код чище и его легче читать, легче писать и меньше глючить. Меньше ошибок, потому что нет вероятности пропустить условие ошибки, поскольку SQL Server теперь делает это за вас.

11 голосов
/ 20 ноября 2008

Это очень общий вопрос, но вот несколько советов:

  • Называйте ваши хранимые процедуры последовательно. Многие используют префикс для определения того, что это хранимая процедура, но не используют 'sp_' в качестве префикса, поскольку он предназначен для основных данных (в любом случае в SQL Server)
  • Установите NOCOUNT, так как это уменьшает количество возможных возвращаемых значений
  • Запросы на основе множеств часто работают лучше, чем курсоры. Этот вопрос более подробно рассматривается в этом вопросе.
  • Если вы ОБЪЯВЛЯЕТЕ переменные для своей хранимой процедуры, используйте хорошие соглашения об именах так же, как и при любых других видах программирования.
  • Позвоните SP, используя их полное имя, чтобы избежать путаницы в том, какой SP следует вызывать, и чтобы повысить производительность SQL Server; это облегчает поиск рассматриваемого ИП.

Конечно, есть гораздо больше. Вот ссылка с более: Советы по оптимизации хранимых процедур SQL Server

3 голосов
/ 20 ноября 2008

В SQL Server я всегда помещаю оператор, который отбрасывает процедуру, если она существует, поэтому я могу легко нажать пересоздать процедуру во время ее разработки. Что-то вроде:

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'usp') AND type in (N'P', N'PC'))
DROP PROCEDURE usp
2 голосов
/ 20 ноября 2008

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

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

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

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

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

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

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

1 голос
/ 31 июля 2012

В SQL Server 2008 используйте конструкцию TRY ... CATCH, которую можно использовать в хранимых процедурах T-SQL, чтобы обеспечить более изящный механизм обработки исключений, чем был доступен в предыдущих версиях SQL Server, проверяя @@ ERROR. (и часто использование операторов GOTO) после каждого оператора SQL.

         BEGIN TRY
             one_or_more_sql_statements
         END TRY
         BEGIN CATCH
             one_or_more_sql_statements
         END CATCH

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

         ERROR_NUMBER()
         ERROR_MESSAGE()
         ERROR_SEVERITY()
         ERROR_STATE()
         ERROR_LINE()
         ERROR_PROCEDURE()

В отличие от @@ error, которая сбрасывается каждым выполняемым оператором, информация об ошибках, полученная функциями ошибок, остается постоянной в любом месте области действия блока CATCH инструкции TRY ... CATCH. Эти функции могут позволить объединить обработку ошибок в единую процедуру, поэтому вам не нужно повторять код обработки ошибок в каждом блоке CATCH.

1 голос
/ 20 ноября 2008

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

Хранимые процедуры - это просто хранимые запросы T-SQL. Поэтому вам нужно больше узнать о T-SQL и его различных функциях и синтаксисах. И, тем более, с точки зрения производительности вам необходимо убедиться, что ваши запросы и базовые структуры данных совпадают таким образом, чтобы обеспечить хорошую производительность. IE, убедитесь, что индексы, отношения, ограничения и т. Д. Реализованы там, где это необходимо.

Понимание того, как использовать инструменты настройки производительности, понимание того, как работают планы выполнения, и тому подобное, как вы попадаете на этот «следующий уровень»

0 голосов
/ 23 мая 2019

Вот некоторый код, доказывающий, что на SQL Server нет многоуровневых ROLLBACK, и он иллюстрирует, как обрабатываются транзакции:


BEGIN TRAN;

    SELECT @@TRANCOUNT AS after_1_begin;

BEGIN TRAN;

    SELECT @@TRANCOUNT AS after_2_begin;

COMMIT TRAN;

    SELECT @@TRANCOUNT AS after_1_commit;

BEGIN TRANSACTION;

    SELECT @@TRANCOUNT AS after_3_begin;

ROLLBACK TRAN;

    SELECT @@TRANCOUNT AS after_rollback;
0 голосов
/ 07 марта 2019

Ниже приведены некоторые лучшие практики,

  1. Избегайте префикса _sp
  2. Включить оператор SET NOCOUNT ON
  3. Старайтесь избегать использования временной таблицы
  4. Старайтесь избегать использования Select * из
  5. Старайтесь избегать использования курсора
  6. используйте правильное индексирование
  7. Правильная обработка ошибок

Дополнительную информацию и примеры кода T-SQL можно найти в этом посте

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...