понедельник, 28 сентября 2009 г.

Неочевидный redirect

На днях коллега ((C) Серега К.) натолкнулся на такую любопытную особенность HttpResponse.Redirect. Предположим, что у нас где-то в коде страницы или компонента ASP.NET есть такой-вот код, вполне на первый взгляд логичный, и не вызывающий бурю протеста:

try

{

     // что-то делаем

     Response.Redirect("url1");

}

catch (Exception ex)

{

     // Обрабатываем все на свете

     Response.Redirect("url2");

}

А теперь вопрос: куда будет перенаправлен запрос в результате нормального (т.е. без исключений) выполнеия кода? url1? А вот и нет - на самом деле url2. Все дело в том, что внутри HttpResponse.Redirect для прекращения обработки запроса вызывается Thread.CurrentThread.Abort. Таким образом наш обработчик "всего на свете", отловит это исключение и перенаправим запрос на url2.

Для того, чтобы избежать подобных неприятностей, необходимо использовать другую версию Redirect - с двумя аргументами:

Redirect(string url, bool endResponse)

второй аргумент следует установить в false.

 

HTH

AlexS

суббота, 8 августа 2009 г.

За что я не люблю визуальные дизайнеры и case-средства для разработки БД

  1. Не дают полного контроля над тем, что происходит с БД.
    Особенно это неприятно/опасно в случае, когда с помощью визуального дизайнера редактируется таблица, в которой есть данные - во многих случаях изменения потребуют пересоздания таблицы. Да, SQL Server Management Studio (в частности) во-первых будет сохранять данные, а во-вторых по-умолчанию запрещает вносить подобные изменения, но все-равно ... я предпочитаю вносить подобные изменения сознательно и сохранить соответствующий скрипт "для будущих поколений" (ибо он наверняка понадобится).
  2. Задают (слишком) много параметров "по-умолчанию".
    Чем больше таких параметров - тем больше вероятность забыть о них, что чревато проблемами. А чем позднее мы обнаруживаем эти проблемы - тем дороже стоит их решение.
  3. Не слишком-то экономят время.
    Нечасто приходится разрабатывать схему большой БД "с нуля". И тем более, это никогда не пироисходит "все и сразу" - схема появляется постепенно. А коли так, то я быстрее напишу SQL код "вручную", чем "дизайнер + генерация + доработка напильником". Если мне нужна картинка, то reverse engineering по уже созданной (из скриптов) базе никто не отменял.
  4. Не способствуют изучению SQL.
    Это, конечно, так себе аргумент, но тем не менее - знание SQL еще никому не мешало, а использование дизайнеров развращает.

HTH,

AlexS

понедельник, 6 июля 2009 г.

Журнал транзакций и запросы на выборку данных

Решил разобраться в том, что же на самом деле представляет из себя журнал транзакций (transaction log) в SQL Server-е.

Весь Books Online/MSDN пронизан мыслью о том, что журнал транзакций - это жизненно важный компонент базы данны и содержит данные о всех транзакциях, которые в ней выполняются. Эта мысль понятна, но меня долгое время занимал вопрос: как же быть с запросами на выборку данных? Они ведь тоже имеют свой уровень изоляции и выполняются в транзакции (пусть и неявной). Записываются ли они в журнал? И если да, то в каком виде и зачем?

Провел пару экспериментов, посмотрел сам журнал и выяснилось: в журнал транзакций не записываются "нерезультативные" тразакции (те, тразакции, которые не приводят к изменению данных). Перефразирую: журнал транзакций содержит "изменения, вносимые в БД", а не "запросы, выполняемые к БД".

Ниже приведу факты, которые (на мой взгляд) существенны для понимания того, чем является и чем не является журнал тразакций в SQL Server:

  1. Запись в журнал производится ДО того, как происходит изменение данных.
  2. Каждая запись содержит идентификатор транзакции, в рамках которой производится данное изменение - это позволяет откатить или заново выполнить любую запротоколированную транзакцию.
  3. Все записи в журнале (и, соответственно, все действия, производимые на физическом увроне с БД) делятся на две категории: те, для отката/повторения которых записывается логическая операция и те, для отката/повторения которых записывается образ данных до и после выполенния операции.
  4. Протоколируются только действия, приводящие к изменению данных - т.е. фактически журнал транзакций содержит результат обработки входящего потока транзакций, а не сами транзакции.

HTH,

AlexS

четверг, 2 июля 2009 г.

Забавные картинки

Недавно разжился лицензией на Red-Gate SQL Toolbelt. Среди прочего, в него входит инструмент с незатейливым названием SQL Dependency Tracker. Название меня особенно не впечатлило, казалось: ну что нового можно сказать о взаимозависимостях объектов в базе данных (хоть sp_MSdependencies и является недокументированной, но используется довольно широко)? Но из любопытства решил посмотреть.

И вот оно: схема зависимостей между объектами в базе данных нашего проекта.

ClientDatabaseDiagram

Вроде бы банальная штука, зато как смотрится! Ну просто форменный computer art! И главное: одного взгляда достаточно для того, чтобы понять вокруг чего крутятся колеса системы.

За всеми "профессиональными"/минималистическими рабочими привычками вроде набора кода вслепую, консолей, запросов, комбинаций клавиш и прочего, как-то забывается великая сила толковой визуализации ... хорошо когда попадается что-то, о ней напоминающее :-)

HTH

AlexS

среда, 24 июня 2009 г.

T-SQL Simple Facts

В последние пару недель читал блоги/Books Online и (заново) открыл для себя некоторые "простые" факты, некоторые из которых здорово меня удивили (признаюсь). Некоторые другие всплыли "по ассоциации", в итоге решил поделиться всеми сразу:

  1. Всем хорошо известен оператор GO, означающий окончание пакета. Так вот, полный синтаксит этого оператора: GO [count] - где count - положительное целое число, указывающее, что предшествующий пакет необходимо выполнить count раз (квадратные скобки указывают, что этот параметр может отсутствовать (что и происходит в ошеломляющем большинстве случаев)).
  2. Для типа данных datetime определена операция сложения, причем вторым аргументом может быть целое число, которое интерпретируется как количество дней. Так:
    DECLARE @D datetime
    SET @D = '2009/06/24 15:00'
    SET @D = @D + 2
    PRINT @D
    выдаст 26 июня 2009, 15:00 ... когда я увидел эту запись, то признаться сильно удивился.
  3. Для добавления данных в таблицу можно использовать следующий синтаксис:
    INSERT INTO <Имя таблицы> DEFAULT VALUES;
    Сработает, конечно, только в том случае, когда для всех полей таблицы, не считая автоинкрементных и timestamp, определены значения по-умолчанию, но все-равно любопытно.
  4. Тип данных timestamp не имеет ничего общего с *nix timestamp (отсчитывающего количество миллисекунд с начала Unix-эры). В SQL Server колонки этого типа данных обновляются автоматически при любых операциях добавления/изменения записи - т.е. это некий аналог "версии строки", за который отвечает сам сервер. Гарантируется уникальность значений полей timestamp в рамках каждой базы данных.
  5. Переменные не покрываются транзакцией:
    DECLARE @i
    SET @i = 10
    BEGIN TRANSACTION
    SET @i = 20
    INSERT INTO SomeTable VALUES (@i)
    ROLLBACK TRANSACTION
    PRINT @i
    напечатает 20 (а не 10, как можно было бы ожидать).
  6. Синтаксис ALTER TABLE ... ALTER позволяет изменить определение поля таблицы но не его имя (что можно было бы ожидать). Для переименования необходимо воспользоваться хранимой процедурой sp_rename (не слишком элегантно, но действенно).

HTH,

AlexS

вторник, 12 мая 2009 г.

Что такое OPENQUERY и чем оно может нам помочь

На днях мне задали задачку (перефразируя):

Есть сервера A, B и С. Сервер A связан с сервером B (т.е. A доступен с B). Сервер B связан с сервером C (т.е. сервер B доступен с C). При этом в соответствии с политикой безопасности сервера A и C "не видят друг-друга" (т.е. связи между ними нет). При этом на сервере A есть некоторые данные, которые нужно получить/обработать в ходе выполнения задания на сервере C. Можно ли такое организовать?

Перефразирую: можно ли каким-либо образом из T-SQL кода открыть соединение к связанному серверу и выполнить какой-то SQL код удаленно? Т.е. в нашем случае: сделать на сервере C что-нибудь такое, чтобы некий SQL код на самом деле выполнился на сервере B (откуда есть доступ к серверу A), а результат вернул нам?

Ответ: можно (только осторожно). Ключом в данном случае является та самая функция OPENQUERY, которая вынесена в заголовок.

Эта функция позволяет выполнить некоторый запрос на связанном сервере (linked server) и использовать его результаты как обычную таблицу:

OPENQUERY (linked_server, 'query')

Например:

SELECT *

FROM

  Table1 t1

  LEFT JOIN OPENQUERY([ServerB], 'SELECT * FROM [ServerA].dbname.dbo.SomeTable') q1 ON q1.ID = t1.ID

Вот еще некоторые факты об OPENQUERY:

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

 

HTH
AlexS

понедельник, 20 апреля 2009 г.

Полнотекстовый поиск: MS Full-Text Search

Вторая часть серии о полнотекстовом поиске несколько задержалась - работа не давала продохнуть. Впрочем, это лирика, приступим к делу.

Итак, я не буду повторять массу открыто доступной информации о Microsoft Full-Text Search (общее описание от Microsoft (eng/рус) и Wikipedia, архитектура), не буду и переписывать простые и не очень примеры. Сосредоточимся на следующем сценарии: у нас имеется некоторое не слишком сложное приложение (веб-приложение, два-три слоя, до несколько Гб данных (до десятка-другого), пара-тройка вспомогательных сервисов) и нам необходимо обеспечить "интеллектуальный поиск" для некоторых сущностей этого приложения. "Интеллектуальность" поиска - маркетинговый прием, которым sales/accounts привлекают пользователей. С чисто технической точки зрения за ним может скрываться следующее:

  • находить не только точные вхождения слов из поискового запроса, но и словоформ (мама/маме/мамы/маму ...)
  • находить не только слова, введенные пользователем, но и синонимы
  • ранжировать результаты поиска по релевантности
  • искать не только по атрибутам сущности, но и в теле файла, который с этой сущностью связан
  • выполнение сложных запросов (вроде "чтобы вот эти два термина находились поблизости", задание весов для слов в поисковом запросе и много чего еще)

Из описания можно заключить что скорее всего этот функционал все-таки в большей степени ориентирован на конечного пользователя (и скорее всего "продвинутого пользователя") и врядли будет использоваться в каких-то вспомогательных сервисах (хотя как знать ...). Теперь рассмотрим насколько сложно/просто будет все это сделать и отгрузить заказчику. Обратимся к следующим основным вопросам: изменения в архитектуре приложения, поддержка/сопровождение и альтернативы.

Архитектура

Здесь Microsoft потрудилась на славу: технология достается нам бесплатно не только в смысле денег (идет в комплекте с SQL Server и есть даже у SQL Server Express (with Advanced Services)) но и в смысле интеграции. Вся сложность ложится на плечи SQL Server-а:

  1. Создаем полнотекстовый каталог
  2. Создаем полнотекстовый индекс
  3. Используем специальные функции (CONTAINS, CONTAINSTABLE, FREETEXT, FREETEXTTABLE) для обращения к индексам из T-SQL кода

Таким образом, код приложения может быть полностью абстрагирован от того, каким образом получены данные: с применением полнотекстового поиска или без него. В обмен на эту простоту на стороне СУБД нас поджидает:

  1. Необходимость проектирования физического уровня: сколько каталогов нам нужно и где они будут храниться
  2. Логический уровень: какие индексы нам нужны, какие колонки индексировать
  3. Подробности алгоритмов обработки данных: стоп-список (noise words (в SQL Server 2005 - фиксированный, один на сервер, в SQL Server 2008 можно создавать пользовательские)), алгоритм разбиения на слова (word breaker), выделение словоформ (stammer), фильтрация содержимого (content filters)

Первые два уровня для начала/в несложных случаях, пожалуй, можно отдать на откуп SQL Server-у, а вот третий пункт таит в себе массу подводных камней, которые могут значительно усложнить жизнь "среднестатистическому разработчику":

  • стоп-список: слова, входящие в него, попросту игнорируются и не включаются в индекс (поэтому неудивительно, что найти их тоже не получится)
  • разбиение на слова: зависит от выбранного языка (локали), может быть нейтральным (по пробелам/знакам препинания)
  • выделение словоформ: зависит от языка, список доступных языков можно узнать из системного представления sys.fulltext_languages (зависит от версии SQL Server, в 2005м нужно было некоторые языки (в том числе и русский) регистрировать вручную). Если выберем "неправильный" язык (например в поле, индексируемом согласно правилам английского языка, будем хранить русский текст), то словоформы работать не будут - все слова будут искаться "как есть" (буквально). Проблемы также возникнут с синонимами и "похожестью" слов.
  • фильтрация содержимого: фактически дает возможность проиндексировать документы, хранящиеся в БД (в поле типа image/varbinary(max)). Хорошая новость заключается в том, что поддерживаются все форматы, которые "понимает" Windows, на которой выполняется SQL Server - нужно лишь задать специальное поле, в котором для данной строки будет храниться формат потока (фактически расширение файла).

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

  1. Хранить данные для всех языков в одних и тех же полях вперемешку - проще всего, но теряется значительная часть функциональности
  2. Для каждого языка создавать отдельное поле/поля (например Title_EN, Title_RU и т.д.) - функциональность остается при нас, но придется довольно много "плясать" вокруг этих данных (в том числе и в приложении - чтобы правильно сохранить/загрузить)

Что бы мы ни выбрали в конечном итоге, за языковыми настройками индексации каждого поля (как и за collation ;-) ) надо следить.

Поддержка/сопровождение

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

  • автоматическое наполнение/обновление (CHANGE_TRACKING AUTO): сервер сам будет следить за актуальностью индексов - хорошее решение со многих точек зрения, но могут возникнуть некоторые неожиданные побочные эффекты, связанные с тем, что к нашей базе данных будет обращаться еще один (системный) процесс, накладывающий некоторое количество блокировок
  • наполнение/синхронизация индексов по запросу (CHANGE_TRACKING MANUAL): больше ответственности, но и больше уверенности в том что и когда мы делаем - может потребоваться в основном при большой загрузке, когда мы хотим максимально разгрузить сервер БД в течении рабочего времени и готовы смириться с некоторой степенью неактуальности информации в индексах

Довольно сложно дела обстоят и с мониторингом/оптимизацией производительности. Microsoft не разглашает внутреннюю структуру хранения полнотекстовых индексов поэтому на них не распространяется опыт оптимизации "обычных" индексов. Единственное, что можно сказать наверняка: чем больше индексов (в том числе и полнотекстовых) "навернуто" на таблицу, тем больше времени будут занимать операции добавления/обновления/удаления данных.

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

image

Выводы делаем сами (впрочем, я думаю и так понятно, что бесплатного в этом мире ничего не бывает).

Альтернативы

Если не рассматривать в качестве альтернативы переход на другую СУБД (в PostgreSQL, Oracle и MySql есть аналогичные технологии), то остается применение неких внешних по отношению к нашему приложению сервисов, производящих индексацию содержимого. Тут можно упомянуть:

  • Windows Search - встроен в Windows (следовательно "бесплатен"), позволяет индексировать файлы, хранящиеся на компьютере
  • Google Desktop - аналогичный продукт от Google
  • Apache Lucene / Solr - индексатор / поисковый сервер от Apache Foundation

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

 

HTH

AlexS