1 / 33

Дмитрий Артемов (dimaa@microsoft)

Надежное программирование, как избежать проблем при разработке приложений на TSQL/ для SQL Server. Дмитрий Артемов (dimaa@microsoft.com). Предупреждение. Примеры кода и решения являются иллюстрациями (возможными, но не единственными способами программирования) Все примеры предельно упрощены.

wanda-barry
Download Presentation

Дмитрий Артемов (dimaa@microsoft)

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Надежное программирование, как избежать проблем при разработке приложений на TSQL/для SQL Server Дмитрий Артемов (dimaa@microsoft.com)

  2. Предупреждение • Примеры кода и решения являются иллюстрациями (возможными, но не единственными способами программирования) • Все примеры предельно упрощены

  3. Надежное программирование для снижения уязвимости кода • Если говорить о надежном программировании БД, существует четыре ключевых элемента, которые позволяют избавиться от ошибок и сделают код менее уязвимым, защищая его от возможных проблем при непреднамеренном использовании • Определитесь в предположениях. • Тестируйте как можно больше вариантов использования. • Используйте короткие, удобные для тестирования и полностью протестированные модули. • При необходимости используйте код повторно, но будьте осторожны

  4. Определитесь в предположениях • Четкое определение предположений – одна из наиболее разрушительных ошибок при разработке ПО, в результате код используется не так как планировалось • Необходимо: • В явном виде определиться с предположениями • Убедиться, что они остаются актуальными на всех этапах разработки • Систематически избавляться от предположений,котрые не важны или более не актуальны

  5. Тщательное тестирование • По мере разработки кода, необходимо включить все наше воображение для определения возможных сценариев незапланированного использования, в попытках «сломать» собственную разработку. • Разработанные сценарии следует включить в программу тестирования. • В процессе тестирования мы должны выяснить как различные изменения могут повлиять на на то,как исполняется код, и научиться как разрабатывать код такми образом, чтобы разрабатывать надежный код, который не «ломается» при изменении, например SET LANGUAGE или ROWCOUNT

  6. Защищайте код от незапланированного использования • Слишком часто мы считаем код готовым, после того как он пройдет несколько простых проверок • Мы не посвящаем достаточно времени на поиск и идентификацию всех возможных, не выходящих за разумные рамки, способов использования нашего кода • Когда наступает неизбежное и наш код используется не так, как мы предполагали, он «ломается»

  7. Защита от незапланированного использования (1/2) CREATE TABLE dbo.Messages ( MessageID INT IDENTITY(1,1) NOT NULL PRIMARY KEY, Subject VARCHAR(30) NOT NULL , Body VARCHAR(100) NOT NULL ) ; CREATE PROCEDURE dbo.SelectMessagesBySubjectBeginning AS SELECT Subject , Body FROM dbo.Messages WHERE Subject LIKE @SubjectBeginning + '%’ • EXEC dbo.SelectMessagesBySubjectBeginning @SubjectBeginning='Next‘ – OK • EXEC dbo.SelectMessagesBySubjectBeginning @SubjectBeginning = '[OT]‘ – Не работает, [] – для LIKE определяют группы символов [a-d] или [abcd] • EXEC dbo.SelectMessagesBySubjectBeginning @SubjectBeginning = '50%‘ – аналогично, % воспринимается как заменитель символа

  8. Защита от незапланированного использования (2/2) ALTER PROCEDURE dbo.SelectMessagesBySubjectBeginning AS SET @ModifiedSubjectBeginning = REPLACE(REPLACE(@SubjectBeginning, '[', '[[]'), '%', '[%]') SELECT @SubjectBeginning AS [@SubjectBeginning] , @ModifiedSubjectBeginning AS [@ModifiedSubjectBeginning] ; SELECT Subject , Body FROM dbo.Messages WHERE Subject LIKE @ModifiedSubjectBeginning + '%'

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

  10. Как SET ROWCOUNT «ломает» триггер (1/2) • Необходимо фиксировать все обновления в таблице • Мы создаем таблицу ObjectsChangeLog, куда будем записывать изменения и триггер,который будет записывать изменения в таблицу ObjectsChangeLog • По умолчанию ROWCOUNT = 0 – возвращает все записи • Если в какой-то момент код меняет значение умолчания для ROWCOUNT и не восстанавливает его, триггер пропустит часть записей • Возможно не сразу, пока число записей < ROWCOUNT • Тем труднее будет отловить эту проблему

  11. Как SET ROWCOUNT «ломает» триггер (2/2) • Не стоит полагаться на судьбу • Мы в явном виде устанавливаем нужное значение и SQL Server восстанвливает то, что было до нас, после завершения триггера ALTER TRIGGER dbo.Objects_UpdTrigger ON dbo.Objects FOR UPDATEAS BEGIN; -- the scope of this setting is the body of the trigger SET ROWCOUNT 0 -- Тело триггера -- На выходе установка ROWCOUNT будет восстановлена • !! Эта настройка считается устаревшей (deprecated) для SQL Server 2008 • !! Using SET ROWCOUNT will not affect DELETE, INSERT, and UPDATE statements in the next release of SQL Server

  12. Off topic • Когда готовил презентацию обнаружился интересный побочный эффект от установки ROWCOUNT

  13. Обновление больше записей, чем планировалось 1/2 UPDATE dbo.Employee SET ManagerID = @ManagerID WHERE FirstName = @FirstName AND LastName = @LastName • Очевидно, разработчик предполагал, что в таблице нет сотрудников с одинаковыми именем и фамилией • Если это не так (что вполне возможно), процедура ассоциирует их все с одним и тем же начальником

  14. Обновление больше записей, чем планировалось 2/2 • Обычно, обнаружив следование некоему предположеню мы можем обеспечить его выполнение или устранить. • Обеспечить выполнение возможно созданием UNIQUE constraint на комбинации полей FirstName и LastName. • Но это расходится с реальной жизнью • Поэтому мы строим код. Чтобы исключить неверное предположение UPDATE dbo.Employee SET ManagerID = @ManagerID WHERE EmployeeID = @EmployeeID • Если EmployeeID – первичный ключ на таблице dbo.Employee, измененный код будет работать корректно

  15. Условия в предложении WHERE могут быть обработаны в любом порядке • Есть целый ряд языков, которые гарантируют определенный порядок обработки логических выражений. SQL не из их числа • Нельзя полагаться,что условия WHERE будут обработаны в определенном порядке. Код, который полагается на порядок обработки условий чреват проблемами SELECT …Orders WHERE ISDATE(VarcharColumn) = 1 AND CAST(VarcharColumn AS DATETIME) = '20090707'; • Если CAST будет обработано перед проверкой ISDATE и строка – некорректная дата, мы получим ошибку INSERT orders values (7, '20099999') SELECT… Msg 242, Level 16, State 3, Line 1 The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. -- Это работает корректно SELECT … FROM Orders WHERE CASE WHEN ISDATE(VarcharColumn) = 1 THEN CAST(VarcharColumn AS DATETIME) END = '20090707' ;

  16. SET, SELECT и пресловутый бесконечный цикл 1/2 • Нельзя рассчитывать что SET иSELECT всегда меняют значения переменных. • Полагаясь на это предположение, мы создаем ненадежный код • SELECT может оставить переменную без изменения,если набор пуст • В такой же ситуации SET присваивает NULL declare @i int set @i = 1 select @i << 1 set @i = (select 1 where 1=2) select @i<< NULL declare @i int select @i =1 select @i << 1 select @i = 1 where 1=2 select @i << 1

  17. SET, SELECT и пресловутый бесконечный цикл 2/2 • Если SET приводит к ошибке, значение переменной также не меняется • Аналогично, если SELECT приводит к ошибке, значение переменной не меняется SET NOCOUNT ON ; DECLARE @i INT ; SELECT @i = -1 ; SELECT @i AS [@i before the assignment] ; SET @i = ( SELECT 1 UNION ALL SELECT 2 ) ; SELECT @i AS [@i after the assignment] ; Т.к. переменная не меняется, мы легко можем попасть в бесконечный цикл SET NOCOUNT ON ; DECLARE @i INT ; SELECT @i = -1 ; SELECT @i AS [@i before the assignment] ; SELECT @i = 1 WHERE ( SELECT 1 AS n UNION ALL SELECT 2 ) = 1 ; SELECT @i AS [@i after the assignment] ; Для ситуации с пустым множеством Используйте явное переназначение Используйте SET вместо SELECT

  18. При назначении переменных лучше SET чем SELECT SELECT @CustomerId = CustomerId FROM dbo.Customers WHERE Position = ‘Manager‘ • Если у нас есть два человека на одинаковых должностях, результат выполнения непредсказуем • Мы не знаем и не можем знать, какой CustomerId попадет в переменную (порядок выборки не определен) • Но запрос будет работать! • SET распознает проблему SET @CustomerId = ( SELECT CustomerId FROM dbo.Customers WHERE PhoneNumber = '(123)456-7890’) Msg 512, Level 16, State 1, Line 16Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression. • Разработчик должен перехватить ошибку

  19. Как «пережить» изменение сигнатуры процедуры CREATE PROCEDURE dbo.SelectCustomersByName @LastName VARCHAR(50) = NULL , @PhoneNumber VARCHAR(50) = NULL EXEC dbo.SelectCustomersByName 'Hansen', -- @LastName '(234)123-4567‘ -- @PhoneNumber EXEC dbo.SelectCustomersByName @LastName = 'Hansen', @PhoneNumber = '(234)123-4567' ; ALTER PROCEDURE dbo.SelectCustomersByName @FirstName VARCHAR(50) = NULL , @LastName VARCHAR(50) = NULL , @PhoneNumber VARCHAR(50) = NULL EXEC dbo.SelectCustomersByName 'Hansen', -- @FirstName '(234)123-4567' ; -- @LastName EXEC dbo.SelectCustomersByName @LastName = 'Hansen', @PhoneNumber = '(234)123-4567' ;

  20. Как «пережить» изменение схемы таблицы • Одна из наиболее частых причин «поломок» кода – изменение схемы таблицы • Добавление новых полей • Изменение определения существующих полей (например типа или размера)

  21. Квалифицированное указание имен полей CREATE TABLE dbo.Shipments ( Barcode VARCHAR(30) NOT NULL PRIMARY KEY,… CREATE TABLE dbo.ShipmentItems ( ShipmentBarcode VARCHAR(30) NOT NULL, Description VARCHAR(100) NULL ) SELECT Barcode , ( SELECT COUNT(*) FROM dbo.ShipmentItems WHERE ShipmentBarcode = Barcode ) AS NumItems FROM dbo.Shipments ; Barcode NumItems ------------- --------- 123456 2 123654 0 ALTER TABLE dbo.ShipmentItems ADD Barcode VARCHAR(30) NULL SELECT Barcode , ( SELECT COUNT(*) FROM dbo.ShipmentItems WHERE ShipmentBarcode = Barcode ) AS NumItemsFROM dbo.Shipments ; Barcode NumItems --------- -------- 123456 0 123654 0 SELECT s.Barcode , ( SELECT COUNT(*) FROM dbo.ShipmentItems AS i WHERE i.ShipmentBarcode = s.Barcode ) AS NumItemsFROM dbo.Shipments AS s ; Barcode NumItems -------- ---------- 123456 2 123654 0

  22. Изменение типа и/или размера CREATE TABLE dbo.Codes (Code VARCHAR(5) NOT NULL , Description VARCHAR(40) NOT NULL , CONSTRAINT PK_Codes PRIMARY KEY ( Code ) ) ALTER TABLE dbo.Codes ALTER COLUMN Code VARCHAR(10) NOT NULL EXEC dbo.SelectCode @Code = '1234567890' ; Code Description ------ --------------------- 12345 Description for 12345 CREATE PROCEDURE dbo.SelectCode @Code VARCHAR(5) AS SELECT Code , Description FROM dbo.Codes WHERE Code = @Code EXEC dbo.SelectCode @Code = '12345' ; Code Description ---------- --------------------- 12345 Description for 12345 Как быть в этой ситуации? Аккуратная документация и тестирование на граничные значения

  23. Когда Snapshot Isolation «ломает» код 1/3 • Триггер, который выполняет проверку, при включенном режиме SNAPSHOT ISOLATION • ALTER DATABASE xxx SET READ_COMMITTED_SNAPSHOT ON; • Может считать данные из “version store” и принять логически неверную запись

  24. Кратко про Snapshot Isolation • Появилась в версии SQL Server 2005 • Позволяет развести “читателей” и “писателей” • За счет размещения последней зафиксированной записи в “хранилище версий” (version store ) в TempDb) • Плюсы • Существенно снижает ожидания при интенсивной конкуренции (читаем из одного места, пишем в другое) • Может существенно снизить число deadlock в ситуации когда блокировка возникает между выборкой и модификацией (те, что обычно лечатся средствами хинта UPDLOCK) • В режиме READ_COMMITTED_SNAPSHOT ON не требует изменения кода (все транзакции, исполняющиеся в режиме READ COMMITTED попадают под раздачу) • Минусы • Может существенно увеличить нагрузку на TempDb • Увеличивает размер самой БД за счет добавления 14 байт к каждой записи • Длинные транзакции могут создавать длинные цепочки версий. Опять нагрузка на TempDb • Обновления могут замедлится из-за необходимости вести версии • Код может работать с ошибками (то, о чем мы говорим сейчас)

  25. Когда Snapshot Isolation «ломает» код 2/3 IF EXISTS (SELECT * FROM inserted AS i INNER JOIN dbo.Developers AS d ON i.AssignedToDeveloperID = d.DeveloperID WHERE d.Status = 'Vacation‘ AND i.Status = 'Active') BEGIN; RAISERROR ('Cannot change ', 16, 1); ROLLBACK; BEGIN TRANSACTION UPDATE Developers --COMMIT Триггер ждет Триггер выдает ошибку Откатывает обновление TICKETS COMMIT BEGIN TRANSACTION UPDATE TICKETS COMMIT

  26. Когда Snapshot Isolation «ломает» код 3/3 • ALTER DATABASE xxx SET READ_COMMITTED_SNAPSHOT ON; BEGIN TRANSACTION UPDATE Developers --COMMIT Мы получаем логически неверные данные Пока транзакция ждет, пытаемся обновить Tickets COMMIT Триггер идет в TempDb, берет данные из Version Store и успешно завершает работу BEGIN TRANSACTION UPDATE TICKETS COMMIT Tempdb Мы получаем логически неверные данные COMMIT READCOMMITTEDLOCK

  27. Тестирование 1/3 • Появление DMV существенно снизило пользу от применения SQL Server Profiler • Однако, пользовательские события могут вдохнуть в него новую жизнь • sp_trace_generateevent @eventid,@userinfo,@userdata • @eventid – int, значение в пределах 82 - 91. Соответствует 10 классам. 82 -UserConfigurable:0, 83 - UserConfigurable:1... • @userinfo - nvarchar(128) для заполнения поля TextData • @userdata - varbinary(8000) для заполнения поля BinaryData

  28. Тестирование 2/3 • Очень полезна в сложной ситуации, когда код падает не всегда • Сбор всех команд за длительный срок создает слишком много данных • Для использования пользовательских событий необходимо иметь права ALTER TRACE • Право редкое, поэтому для его применения можно создать оболочку для вызова sp_trace_generateevent • Оболочке дадим права на базе сертификата

  29. IF @@ROWCOUNT = 0 EXEC ThrowEvent 82, N'No data inserted into MyTable', 0x0000 IF @@ROWCOUNT = 0 EXEC ThrowEvent 82, N'No data inserted into MyTable', 0x0000 Тестирование 3/3 USE master GO CREATE CERTIFICATE ALTER_TRACE_CERT ENCRYPTION BY PASSWORD='-UsE_a!sTr0Ng_PwD-or-3~' WITH SUBJECT='Certificate for ALTER TRACE', START_DATE='20000101', EXPIRY_DATE='99990101' GO CREATE LOGIN ALTER_TRACE_LOGIN FROM CERTIFICATE ALTER_TRACE_CERT GO GRANT ALTER TRACE TO ALTER_TRACE_LOGIN GO GRANT AUTHENTICATE SERVER TO ALTER_TRACE_LOGIN GO CREATE PROCEDURE ThrowEvent @eventid INT, @userinfonvarchar(128), @userdatavarbinary(8000) AS BEGIN EXEC sp_trace_generateevent @eventid = @eventid, @userinfo = @userinfo, @userdata = @userdata END GO ADD SIGNATURE TO ThrowEvent BY CERTIFICATE ALTER_TRACE_CERT WITH PASSWORD='-UsE_a!sTr0Ng_PwD-or-3~' GO GRANT EXEC ON ThrowEvent TO [public] GO -- Отладочный код в основной процедуре IF @@ROWCOUNT = 0 EXEC ThrowEvent 82, N'No data inserted into MyTable', 0x0000

  30. Все, что я только что рассказал, и более того, можно найти в этой книге • Бесплатно главы 1-8 на http://www.simple-talk.com/books/sql-books/defensive-database-programming/

  31. Спасибо! Есть вопросы?

  32. Официальные курсы и сертификация Microsoft • Более 300 официальных курсов Microsoft доступно в России. • Официальные курсы можно прослушать только в авторизованных учебных центрах Microsoft • под руководством опытного сертифицированного инструктора Microsoft • интенсивное обучение с акцентом на практику • более 80-и учебных центров более чем в 20-и городах России (+ дистанционные и выездные курсы) • Сертификат Microsoft - показатель квалификации ИТ-специалиста для работодателя . • Microsoft предлагает гибкую систему сертификаций. • Все курсы, учебные центры и центры тестирования: www.microsoft.com/rus/learning 40 57 % % Доказательство № 75 Доказательство № 119 • рекрутеров считают сертификацию сотрудников одним из критериев для повышения в должности • сертифицированных специалистов считают, что сертификация помогла им получить работу или повышение

  33. Специальные предложения • Сертификационный пакет со вторым шансом • Пакеты экзаменационных ваучеров со скидкой от 15 до 20% и бесплатной пересдачей («вторым шансом»). Все экзамены сдаются одним человеком. • Сэкономьте 15% на сертификации вашей ИТ-команды • Пакет из 10-и экзаменационных ваучеров со скидкой 15% для сотрудников ИТ-отдела. «Второй шанс» включен. Ваучеры можно произвольно распределять между сотрудниками. • Microsoft Certified Career Conference • Первая 24-часовая глобальная виртуальная конференция с 18 ноября с 15.00 (моск. время) по 19 ноября 2010 г. • Сессии по технологиям и построению карьеры • Скидка 50% для сертифицированных специалистов Microsoft и студентов • Бесплатная подписка на TechNet для слушателей официальных курсов • Некоторые курсы по SharePoint, Windows 7; WindowsServer 2008; SQL Server 2008 • Детали: www.microsoft.com/rus/learning С 22 ноября 2010 г. – подписка TechNet бесплатно для слушателей курсов. Количество ограничено!

More Related