33
Надежное программирование, как избежать проблем при разработке приложений на TSQL/для SQL Server Дмитрий Артемов ([email protected])

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

Embed Size (px)

DESCRIPTION

Надежное программирование, как избежать проблем при разработке приложений на TSQL/ для SQL Server. Дмитрий Артемов ([email protected]). Предупреждение. Примеры кода и решения являются иллюстрациями (возможными, но не единственными способами программирования) Все примеры предельно упрощены. - PowerPoint PPT Presentation

Citation preview

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

Надежное программирование, как избежать проблем при разработке приложений на TSQL/для SQL Server

Дмитрий Артемов ([email protected])

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

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

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

Надежное программирование для снижения уязвимости кода

Если говорить о надежном программировании БД, существует четыре ключевых элемента, которые позволяют избавиться от ошибок и сделают код менее уязвимым, защищая его от возможных проблем при непреднамеренном использовании

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

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

Определитесь в предположениях

Четкое определение предположений – одна из наиболее разрушительных ошибок при разработке ПО, в результате код используется не так как планировалосьНеобходимо:

В явном виде определиться с предположениямиУбедиться, что они остаются актуальными на всех этапах разработкиСистематически избавляться от предположений, котрые не важны или более не актуальны

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

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

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

Защищайте код от незапланированного использования

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

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

Защита от незапланированного использования (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 ASSELECT 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%‘ – аналогично, % воспринимается как заменитель символа

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

Защита от незапланированного использования (2/2)

ALTER PROCEDURE dbo.SelectMessagesBySubjectBeginning ASSET @ModifiedSubjectBeginning =

REPLACE(REPLACE(@SubjectBeginning, '[', '[[]'), '%', '[%]')

SELECT @SubjectBeginning AS [@SubjectBeginning] , @ModifiedSubjectBeginning AS [@ModifiedSubjectBeginning] ; SELECT Subject , Body FROM dbo.Messages

WHERE Subject LIKE @ModifiedSubjectBeginning + '%'

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

Защита от изменений в настройках SQL Server

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

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

Как SET ROWCOUNT «ломает» триггер (1/2)

Необходимо фиксировать все обновления в таблицеМы создаем таблицу ObjectsChangeLog, куда будем записывать изменения и триггер, который будет записывать изменения в таблицу ObjectsChangeLogПо умолчанию ROWCOUNT = 0 – возвращает все записиЕсли в какой-то момент код меняет значение умолчания для ROWCOUNT и не восстанавливает его, триггер пропустит часть записей

Возможно не сразу, пока число записей < ROWCOUNTТем труднее будет отловить эту проблему

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

Как SET ROWCOUNT «ломает» триггер (2/2)

Не стоит полагаться на судьбуМы в явном виде устанавливаем нужное значение и SQL Server восстанвливает то, что было до нас, после завершения триггера

ALTER TRIGGER dbo.Objects_UpdTrigger ON dbo.Objects FOR UPDATE AS 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

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

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

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

Обновление больше записей, чем планировалось 1/2UPDATE dbo.Employee SET ManagerID =

@ManagerID WHERE FirstName = @FirstName AND LastName =

@LastNameОчевидно, разработчик предполагал, что в таблице нет сотрудников с одинаковыми именем и фамилиейЕсли это не так (что вполне возможно), процедура ассоциирует их все с одним и тем же начальником

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

Обновление больше записей, чем планировалось 2/2Обычно, обнаружив следование некоему предположеню

мы можем обеспечить его выполнение или устранить.Обеспечить выполнение возможно созданием UNIQUE constraint на комбинации полей FirstName и LastName.Но это расходится с реальной жизньюПоэтому мы строим код. Чтобы исключить неверное предположение

UPDATE dbo.EmployeeSET ManagerID = @ManagerID WHERE EmployeeID = @EmployeeID

Если EmployeeID – первичный ключ на таблице dbo.Employee, измененный код будет работать корректно

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

Условия в предложении 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 1The conversion of a varchar data type to a datetime data type resulted in an

out-of-range value.

-- Это работает корректноSELECT … FROM Orders WHERECASE WHEN ISDATE(VarcharColumn) = 1 THEN CAST(VarcharColumn AS DATETIME) END = '20090707' ;

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

SET, SELECT и пресловутый бесконечный цикл 1/2

Нельзя рассчитывать что SET и SELECT всегда меняют значения переменных.Полагаясь на это предположение, мы создаем ненадежный кодSELECT может оставить переменную без изменения, если набор пустВ такой же ситуации SET присваивает NULLdeclare @i int

set @i = 1select @i << 1set @i = (select 1 where 1=2)select @i << NULL

declare @i intselect @i =1select @i << 1select @i = 1 where 1=2select @i << 1

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

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 = 1WHERE ( SELECT 1 AS n UNION ALL SELECT 2 ) = 1 ;SELECT @i AS [@i after the assignment] ;

Т.к. переменная не меняется, мы легко можем попасть в бесконечный цикл

Для ситуации с пустым множеством

1. Используйте явное переназначение

2. Используйте SET вместо SELECT

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

При назначении переменных лучше 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.Разработчик должен перехватить ошибку

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

Как «пережить» изменение сигнатуры процедуры

CREATE PROCEDURE dbo.SelectCustomersByName @LastName VARCHAR(50) = NULL , @PhoneNumber VARCHAR(50) = NULL

ALTER PROCEDURE dbo.SelectCustomersByName @FirstName VARCHAR(50) = NULL , @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' ;

EXEC dbo.SelectCustomersByName 'Hansen', -- @FirstName '(234)123-4567' ; -- @LastName

EXEC dbo.SelectCustomersByName @LastName = 'Hansen', @PhoneNumber = '(234)123-4567' ;

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

Как «пережить» изменение схемы таблицы

Одна из наиболее частых причин «поломок» кода – изменение схемы таблицы

Добавление новых полейИзменение определения существующих полей (например типа или размера)

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

Квалифицированное указание имен полей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 2123654 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 0123654 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 2123654 0

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

Изменение типа и/или размера

CREATE TABLE dbo.Codes (Code VARCHAR(5) NOT NULL , Description VARCHAR(40) NOT NULL , CONSTRAINT PK_Codes PRIMARY KEY ( Code ) )

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

ALTER TABLE dbo.Codes ALTER COLUMN Code VARCHAR(10) NOT NULL

EXEC dbo.SelectCode @Code = '1234567890' ;

Code Description------ ---------------------12345 Description for 12345

Как быть в этой ситуации?Аккуратная документация и тестирование на граничные значения

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

Когда Snapshot Isolation «ломает» код 1/3

Триггер, который выполняет проверку, при включенном режиме SNAPSHOT ISOLATION

ALTER DATABASE xxx SET READ_COMMITTED_SNAPSHOT ON;

Может считать данные из “version store” и принять логически неверную запись

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

Кратко про Snapshot IsolationПоявилась в версии SQL Server 2005

Позволяет развести “читателей” и “писателей”За счет размещения последней зафиксированной записи в “хранилище версий” (version store ) в TempDb)

ПлюсыСущественно снижает ожидания при интенсивной конкуренции (читаем из одного места, пишем в другое)Может существенно снизить число deadlock в ситуации когда блокировка возникает между выборкой и модификацией (те, что обычно лечатся средствами хинта UPDLOCK)В режиме READ_COMMITTED_SNAPSHOT ON не требует изменения кода (все транзакции, исполняющиеся в режиме READ COMMITTED попадают под раздачу)

МинусыМожет существенно увеличить нагрузку на TempDbУвеличивает размер самой БД за счет добавления 14 байт к каждой записиДлинные транзакции могут создавать длинные цепочки версий. Опять нагрузка на TempDbОбновления могут замедлится из-за необходимости вести версииКод может работать с ошибками (то, о чем мы говорим сейчас)

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

Когда 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 TRANSACTIONUPDATE Developers--COMMITBEGIN TRANSACTIONUPDATE TICKETSCOMMIT

Триггер

ждет

Триггер выдает ошибку

Откатывает обновление

TICKETS

COMMIT

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

Когда Snapshot Isolation «ломает» код 3/3

ALTER DATABASE xxx SET READ_COMMITTED_SNAPSHOT ON;

BEGIN TRANSACTIONUPDATE Developers--COMMIT

BEGIN TRANSACTIONUPDATE TICKETSCOMMIT

Мы получаем логически

неверные данныеCOMMIT

Tempdb

Мы получаем логически неверные

данныеCOMMIT

Триггер идет в TempDb, берет

данные из Version Store и успешно

завершает работу

Пока транзакция ждет, пытаемся обновить Tickets

READCOMMITTEDLOCK

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

Тестирование 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

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

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

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

Тестирование 3/3USE masterGOCREATE CERTIFICATE ALTER_TRACE_CERTENCRYPTION BY PASSWORD='-UsE_a!sTr0Ng_PwD-or-3~'WITH SUBJECT='Certificate for ALTER TRACE', START_DATE='20000101', EXPIRY_DATE='99990101'GOCREATE LOGIN ALTER_TRACE_LOGINFROM CERTIFICATE ALTER_TRACE_CERTGOGRANT ALTER TRACE TO ALTER_TRACE_LOGINGOGRANT AUTHENTICATE SERVER TO ALTER_TRACE_LOGINGO

CREATE PROCEDURE ThrowEvent @eventid INT, @userinfo nvarchar(128), @userdata varbinary(8000)ASBEGIN EXEC sp_trace_generateevent @eventid = @eventid, @userinfo = @userinfo, @userdata = @userdataENDGOADD SIGNATURE TO ThrowEventBY CERTIFICATE ALTER_TRACE_CERTWITH PASSWORD='-UsE_a!sTr0Ng_PwD-or-3~'GOGRANT EXEC ON ThrowEvent TO [public]GO

IF @@ROWCOUNT = 0 EXEC ThrowEvent 82, N'No data inserted into MyTable', 0x0000IF @@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

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

Все, что я только что рассказал, и более того, можно найти в этой книгеБесплатно главы 1-8 на

http://www.simple-talk.com/books/sql-books/defensive-database-programming/

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

СПАСИБО! ЕСТЬ ВОПРОСЫ?

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

Официальные курсы и сертификация Microsoft

Более 300 официальных курсов Microsoft доступно в России. Официальные курсы можно прослушать только в авторизованных учебных центрах Microsoft

под руководством опытного сертифицированного инструктора Microsoftинтенсивное обучение с акцентом на практикуболее 80-и учебных центров более чем в 20-и городах России (+ дистанционные и выездные курсы)

Сертификат Microsoft - показатель квалификации ИТ-специалиста для работодателя .

• Microsoft предлагает гибкую систему сертификаций.

• Все курсы, учебные центры и центры тестирования: www.microsoft.com/rus/learning

40% Доказательство № 75

сертифицированных специалистов считают, что сертификация помогла им получить работу или повышение

57% Доказательство № 119

рекрутеров считают сертификацию сотрудников одним из критериев для повышения в должности

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

Специальные предложенияСертификационный пакет со вторым шансом

Пакеты экзаменационных ваучеров со скидкой от 15 до 20% и бесплатной пересдачей («вторым шансом»). Все экзамены сдаются одним человеком.

Сэкономьте 15% на сертификации вашей ИТ-команды

Пакет из 10-и экзаменационных ваучеров со скидкой 15% для сотрудников ИТ-отдела. «Второй шанс» включен. Ваучеры можно произвольно распределять между сотрудниками.

Microsoft Certified Career ConferenceПервая 24-часовая глобальная виртуальная конференция с 18 ноября с 15.00 (моск. время) по 19 ноября 2010 г.Сессии по технологиям и построению карьерыСкидка 50% для сертифицированных специалистов Microsoft и студентов

Бесплатная подписка на TechNet для слушателей официальных курсов

Некоторые курсы по SharePoint, Windows 7; Windows Server 2008; SQL Server 2008

Детали: www.microsoft.com/rus/learning

С 22 ноября 2010 г. – подписка TechNet

бесплатно для слушателей курсов.

Количество ограничено!