<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6076140503946495214</id><updated>2011-08-30T14:11:38.678+03:00</updated><category term='Tools'/><category term='SMO'/><category term='Performance'/><category term='Events'/><category term='SQL'/><category term='MS SQL Server'/><category term='dbbuilder'/><category term='Разное'/><category term='Поделки'/><category term='.NET'/><category term='sqlite'/><category term='ADO.NET'/><title type='text'>Building (around) Databases</title><subtitle type='html'>Блог об обработке данных и разработке приложений</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>34</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-7456154532332408716</id><published>2010-12-01T11:02:00.004+02:00</published><updated>2010-12-01T13:50:50.043+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='Разное'/><title type='text'>“В жизни всегда есть место для подвига” © или как реализовать Singleton по-новому</title><content type='html'>&lt;p&gt;Тема реализации шаблона &lt;a href="http://en.wikipedia.org/wiki/Singleton_pattern" target="_blank"&gt;Singleton&lt;/a&gt; так или иначе затрагивает жизнь любого разработчика – толи на собеседовании спросят (или самом спросить придется), толи в реальном проекте найдется применение (не приведи Господи). О том, как этого зверя вырастить, написано немало – и в &lt;a href="http://msdn.microsoft.com/en-us/library/ff650316.aspx" target="_blank"&gt;MSDN&lt;/a&gt;, и в прочих &lt;a href="http://csharpindepth.com/Articles/General/Singleton.aspx" target="_blank"&gt;интернетах&lt;/a&gt; (последняя статься считается канонической и ранее располагалась по другому &lt;a href="http://www.yoda.arachsys.com/csharp/singleton.html" target="_blank"&gt;адресу&lt;/a&gt;). В какой-то момент казалось (мне по крайней мере), что придумать что-нибудь новое в этой области невозможно. Ошибся.&lt;/p&gt;  &lt;p&gt;Недавно на одном из проектов заказчик прислал кусок [псевдо]кода, в котором кроме всего прочего содержалась крайне любопытная реализация привычного шаблона (тот еще затейник – архитектура решения тоже заставляем “много думать”). В чистом виде (убирая конкретику проекта и применяя привычные для .NET схемы именования и форматирования) это выглядит так:&lt;/p&gt;  &lt;br /&gt;&lt;br /&gt;&lt;pre class="brush:csharp"&gt;&lt;br /&gt;&lt;br /&gt;public interface ISingleton&lt;br /&gt;{&lt;br /&gt; &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class Singleton : ISingleton&lt;br /&gt;{&lt;br /&gt;  public delegate ISingleton InstanceGetterDelegate();&lt;br /&gt;&lt;br /&gt;  private static Singleton _singleton;&lt;br /&gt;  private static InstanceGetterDelegate _instanceGetter;&lt;br /&gt;  static Singleton()&lt;br /&gt;  {&lt;br /&gt;    var type = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;&lt;br /&gt;    // assign the closure that executes on the first call of GetInstance() method&lt;br /&gt;    _instanceGetter = () =&amp;gt;&lt;br /&gt;    {&lt;br /&gt;      Singleton instance = null;&lt;br /&gt;      lock (type)&lt;br /&gt;      {&lt;br /&gt;        var rslt = Interlocked.CompareExchange&amp;lt;Singleton&amp;gt;(ref _singleton, null, null);&lt;br /&gt;        if (rslt == null) // indicates is first call&lt;br /&gt;        {&lt;br /&gt;          instance = new Singleton(/* init arguments go here*/);&lt;br /&gt;          Interlocked.CompareExchange&amp;lt;Singleton&amp;gt;(ref _singleton, null, instance);&lt;br /&gt;&lt;br /&gt;          // now replace with a closure that executes on all subsequent calls of GetInstance() method&lt;br /&gt;          InstanceGetterDelegate fn = () =&amp;gt;&lt;br /&gt;          {&lt;br /&gt;             return Interlocked.CompareExchange&amp;lt;Singleton&amp;gt;(ref _singleton, null, null);&lt;br /&gt;          };&lt;br /&gt;          Interlocked.Exchange&amp;lt;InstanceGetterDelegate&amp;gt;(ref _instanceGetter, fn);&lt;br /&gt;        }&lt;br /&gt;        else&lt;br /&gt;        {&lt;br /&gt;          instance = rslt; // was already set with singleton instance&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;      return instance;&lt;br /&gt;    };&lt;br /&gt;  }&lt;br /&gt;  private Singleton() { } // disable default instance constructor&lt;br /&gt;  public static ISingleton GetInstance() { return _instanceGetter(); }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;И вот тут, что называется, “накрывает”. Я бы назвал этот способ “JavaScript-style Singleton” (конечно же за использования замыканий). В целом (за исключение блокировки по Type (о чем я уже &lt;a href="http://dbbuilder.blogspot.com/2010/02/on-type-or-not-on-type.html" target="_blank"&gt;писал&lt;/a&gt;) и использования рефлексии в статическом конструкторе) реализация выглядит любопытной и уж во всяком случае расширяет горизонт.&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;HTH, AlexS&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-7456154532332408716?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/7456154532332408716/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/12/singleton.html#comment-form' title='Комментарии: 3'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/7456154532332408716'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/7456154532332408716'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/12/singleton.html' title='“В жизни всегда есть место для подвига” © или как реализовать Singleton по-новому'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-6862629538747513953</id><published>2010-11-27T00:43:00.001+02:00</published><updated>2010-11-27T00:43:13.416+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='sqlite'/><title type='text'>sqlite: приятные мелочи в области агрегирующих функций</title><content type='html'>&lt;p&gt;Продолжаю очень приятно удивляться sqlite. Очередным поводом стали агрегирующие функции:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;есть две функции для вычисления сумм: традиционная sum() и чуть менее традиционная total() – обе суммируют не-NULL значения, но первая (в соответствии со стандартом SQL) возвращает пустой набор данных в случае, если таких значений не было, а вот вторая всегда возвращает 0.0 (что в некоторых случаях может быть весьма удобно). Кроме того, функция total() всегда возвращает double и не подвержена арифметическому переполнению;&lt;/li&gt;    &lt;li&gt;есть крайне полезная функция group_concat(field[, separator]), которая объединяет значения поля в одну строку. Разделитель по-умолчанию – запятая. Причем в варианте с разделителем по-умолчанию можно использовать еще и DISTINCT:     &lt;br /&gt;SELECT group_concat(DISTINCT user_name) FROM …. GROUP BY …      &lt;br /&gt;Замечу, что в SQL Server-е (горячо и искренне мною любимом) нет простого способа сделать тоже самое – только через custom aggregate (т.е. специальную сборку, которую писать/разворачивать).&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;В общем: чем больше смотрю - тем больше нравится.&lt;/p&gt;  &lt;p&gt;HTH, AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-6862629538747513953?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/6862629538747513953/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/11/sqlite.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/6862629538747513953'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/6862629538747513953'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/11/sqlite.html' title='sqlite: приятные мелочи в области агрегирующих функций'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-8982901561595121684</id><published>2010-11-16T11:16:00.001+02:00</published><updated>2010-11-16T11:51:57.466+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><title type='text'>Эволюция MS Full-Text Search: доступ к анализатору полнотекстовых запросов</title><content type='html'>&lt;p&gt;Так и работает эволюция: незаметно и по чуть-чуть. Недавно на StackOverflow задали &lt;a href="http://stackoverflow.com/questions/4158929/use-sql-server-fts-stemmer/" target="_blank"&gt;вопрос&lt;/a&gt; (в вольном переводе): &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Можно ли получить доступ к стеммеру, который используется в SQL Server FullText Search?&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Моей первой мыслью было “нельзя”. Но на всякий случай решил свериться с MSDN. Так вот оказалось, что “почти можно”: в SQL Server 2008 в FullText Search помимо радикального изменения архитектуры, внесли менее заметные, но полезные изменения. В частности, была добавлена следующая системная табличная &lt;a href="http://technet.microsoft.com/en-us/library/cc280463.aspx" target="_blank"&gt;&lt;u&gt;функция&lt;/u&gt;&lt;/a&gt;:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;sys.dm_fts_parser(&lt;span style="color: #006080"&gt;'query_string'&lt;/span&gt;, lcid, stoplist_id, accent_sensitivity)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Она-то и позволяет обратиться к стеммеру (точнее к синтаксическому анализатору полнотекстовых запросов). Вот как это работает (лучше один раз увидеть, как говорится):&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;span style="color: #0000ff"&gt;select&lt;/span&gt; * &lt;span style="color: #0000ff"&gt;from&lt;/span&gt; sys.dm_fts_parser(&lt;span style="color: #006080"&gt;'FORMSOF(inflectional, Worked)'&lt;/span&gt;, 1033, 0, 0)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Т.е. мы запросили все словоформы слова “worked” в английской локали (locId = 1033). И вот какие ключевые слова будут соответствовать этому запросу:&lt;/p&gt;  &lt;p align="center"&gt;&lt;a href="http://lh4.ggpht.com/_0v8-WQDReFE/TOJL8gbf0nI/AAAAAAAAHCQ/OP9_tU66LWw/s1600-h/image%5B3%5D.png"&gt;&lt;img style="background-image: none; border-right-width: 0px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="image" border="0" alt="image" src="http://lh3.ggpht.com/_0v8-WQDReFE/TOJL-l0qmwI/AAAAAAAAHCU/WEo1G-yVA1Y/image_thumb%5B1%5D.png?imgmax=800" width="644" height="115" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Было бы еще хорошо иметь возможность заглянуть внутрь самого индекса (как это делает Luke для Lucene), но даже с этими средствами жить&amp;#160; становится намного веселее.&lt;/p&gt;  &lt;p&gt;HTH, AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-8982901561595121684?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/8982901561595121684/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/11/ms-full-text-search.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8982901561595121684'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8982901561595121684'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/11/ms-full-text-search.html' title='Эволюция MS Full-Text Search: доступ к анализатору полнотекстовых запросов'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_0v8-WQDReFE/TOJL-l0qmwI/AAAAAAAAHCU/WEo1G-yVA1Y/s72-c/image_thumb%5B1%5D.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-1423010054912276860</id><published>2010-07-29T15:01:00.001+03:00</published><updated>2010-07-29T15:01:36.617+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Чем точнее указан путь – тем быстрее можно по нему идти ((С) Кэп)</title><content type='html'>&lt;p&gt;Недавно попросили помочь с оптимизацией обработки XML в SQL Server-е. Запрос выглядит примерно следующим образом:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;SELECT      &lt;br /&gt;&amp;#160; nref.value('./../../@id', 'int') AS articleid,      &lt;br /&gt;&amp;#160; nref.value('type[1]', 'nvarchar(256)') AS type,      &lt;br /&gt;&amp;#160; nref.value('theme[1]', 'varchar(256)') AS theme,      &lt;br /&gt;&amp;#160; nref.value('score[1]', 'varchar(32)') AS score      &lt;br /&gt;INTO #tmpTheme      &lt;br /&gt;FROM @bulk_xml.nodes('//theme') AS R(nref)&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Очень простое действие – указание точного XPath выражения вместо обобщенного ('//theme') позволяет ускорить работу данного запроса в 1.5 – 2 раза.&lt;/p&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-1423010054912276860?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/1423010054912276860/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/07/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1423010054912276860'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1423010054912276860'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/07/blog-post.html' title='Чем точнее указан путь – тем быстрее можно по нему идти ((С) Кэп)'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-4761430518586913217</id><published>2010-07-11T10:45:00.001+03:00</published><updated>2010-07-11T10:45:16.258+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><title type='text'>Заблуждения: “restore from database”</title><content type='html'>&lt;p&gt;Недавно пришлось развенчивать миф: “restore from database” (см. скриншот) позволяет использовать журнал транзакций [базы данных А] для “point in time recovery” [базы банных Б, восстановленной из резервной копии базы данных А].&lt;/p&gt;  &lt;p align="center"&gt;&lt;a href="http://lh3.ggpht.com/_0v8-WQDReFE/TDl2etvLDoI/AAAAAAAAGos/_vumBbataZg/s1600-h/image%5B4%5D.png"&gt;&lt;img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" border="0" alt="image" src="http://lh5.ggpht.com/_0v8-WQDReFE/TDl2iQXSdMI/AAAAAAAAGow/Li1niwlv1TQ/image_thumb%5B2%5D.png?imgmax=800" width="398" height="359" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Контекст, в котором происходило обсуждение, тоже представляет определенный интерес Есть база данных [пусть будет “База А”], которая довольно сильно нагружена. Она работает в режиме восстановления от сбоев “Full”. Каждую ночь делается полный бэкап этой базы, никакие другие бэкапы (дифференциальный, журнала) не делаются. Совет перевести базу в режим восстановления “Simple” (нагрузка на подсистему ввода/вывода меньше, размер журнала меньше, по возможностям&amp;#160; восстановления ничего не теряем) наталкивается на следующий аргумент&amp;#160; (вольный перевод)&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;“если База А будет повреждена, то мы сможем восстановить ее из резервной копии и донакатить транзакции из журнала, поэтому Simple нам не подходит”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Так вот, “эта штука так не работает”. Опция “from database” просто-напросто подхватывает историю резервных копий выбранной базы данных – так что пользователю не нужно лазить по дискам в поисках какого-то конкретного бэкапа (или самого свежего бэкапа). Журнал транзакций “намертво” привязан к базе и только SQL Server может что-то с ним делать (пользователь может только посмотреть что в нем, да и то используя недокументированные функции). Единственный [для пользователя] способ использовать информацию об операциях, хранящуюся в журнале, – сделать резервную копию журнала &lt;strong&gt;&lt;em&gt;после создания полного бэкапа базы данных&lt;/em&gt;&lt;/strong&gt;. В этом случае резервная копия журнала (а не сам журнал!) может быть использована для повторного выполнения операций в базе, восстановленной из этого полного бэкапа (т.е. “point in time recovery”).&lt;/p&gt;  &lt;p&gt;HTH, AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-4761430518586913217?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/4761430518586913217/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/07/restore-from-database.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/4761430518586913217'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/4761430518586913217'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/07/restore-from-database.html' title='Заблуждения: “restore from database”'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_0v8-WQDReFE/TDl2iQXSdMI/AAAAAAAAGow/Li1niwlv1TQ/s72-c/image_thumb%5B2%5D.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-6103185140711433457</id><published>2010-05-16T23:53:00.001+03:00</published><updated>2010-05-16T23:53:55.120+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Право на жизнь для табличных переменных</title><content type='html'>&lt;p&gt;С момента появления в SQL Server табличных переменных я все никак не мог понять (для себя): зачем же они прямо &lt;strong&gt;&lt;em&gt;так&lt;/em&gt;&lt;/strong&gt; нужны (по большому счету). Аргумент “передавать много параметров в хранимую процедуру в табличной форме” звучал (и до сих пор звучит) не слишком убедительно: никто не отменял упаковки/распаковки (в случае, когда вызов идет из кода) и … собственно таблиц (в случае, когда мы вызываем хранимку из другой хранимки/скрипта).&lt;/p&gt;  &lt;p&gt;Недавно на StackOverflow был задан &lt;a href="http://stackoverflow.com/questions/2809659/logging-into-table-in-sql-server-trigger" target="_blank"&gt;вопрос&lt;/a&gt;, который в вольном переводе звучит следующим образом: как мне сохранить какие-то данные из транзакции, которая затем будет отменена? Сценарий, который стоит за этом вопросом, более чем реален: протоколирование. Ответом на этот вопрос и являются табличные переменные: изменения значений переменных не является частью транзакции (и, соответственно, не подлежат отмене). &lt;/p&gt;  &lt;p&gt;Проверил: да, таки работает. Но тут как в том старом анекдоте: “ложечки нашлись, но осадок остался”. Табличная переменная на самом деле – это временная таблица (что легко проверить). При этом изменения, вносимые в “обычные” временные таблицы протоколируются и являются частью транзакции, в которой эти изменения происходят (что тоже легко проверяется). Получается некоторое противоречие: табличная переменная – это временная таблица, но изменения, которые в нее вносятся не протоколируются. Покопавшись в гуглоридере, нашел (отмеченный мною же!) &lt;a href="http://blogs.msdn.com/sqlserverstorageengine/archive/2008/03/30/sql-server-table-variable-vs-local-temporary-table.aspx" target="_blank"&gt;пост в блоге SQL Server Storage Engine&lt;/a&gt; на эту тему – “перечитал и переосмыслил” :-)&lt;/p&gt;  &lt;p&gt;HTH&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-6103185140711433457?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/6103185140711433457/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/05/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/6103185140711433457'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/6103185140711433457'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/05/blog-post.html' title='Право на жизнь для табличных переменных'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-595306181805699938</id><published>2010-04-06T14:08:00.001+03:00</published><updated>2010-04-06T17:35:21.247+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Разное'/><title type='text'>Бытовые особенности SQLite</title><content type='html'>&lt;p&gt;В последние пару недель работаю с &lt;a href="http://www.sqlite.org" target="_blank"&gt;SQLite&lt;/a&gt; (не то чтобы очень плотно, но плотнее, чем “просто поковырять”). Штуковина безусловно полезная и нужная, но не без особенностей. Ниже приведу список того, обо что споткнулся сам:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Нет типа данных DateTime или чего-то похожего – все даты нужно хранить в виде строки; проблема мелкая, но стоит о ней помнить. Более подробное описание типов данных SQLite лежит &lt;a href="http://www.sqlite.org/datatype3.html" target="_blank"&gt;здесь&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;Нет функции поиска подстроки/символа в строке (т.е. ничего, аналогичного CHARINDEX в SQL Server-е). Можно, конечно, самому написать такую функцию (на C) и подключить ее на лету, но … в общем лучше бы она была сразу. &lt;/li&gt;    &lt;li&gt;Не поддерживается [ставшая уже привычной] конструкция UPDATE|DELETE|INSERT …. FROM …. &lt;/li&gt;    &lt;li&gt;Крайне любопытно организовано хранение данных, в частности – в каждой таблице неявно присутствует “&lt;a href="http://www.sqlite.org/autoinc.html" target="_blank"&gt;автоматический первичный ключ&lt;/a&gt;” (поле ROWID), который к тому же является “кластерным” (в привычных терминах). Причем явное объявление “целочисленного, автоинкрементного первичного ключа” в большинстве случаев – просто псевдоним для ROWID. &lt;/li&gt;    &lt;li&gt;Есть доступ к метаданным (их, правда, немного) – таблица sqlite_master. &lt;/li&gt;    &lt;li&gt;Сравнение строк может производиться как с учетом регистра, так и без оного – зависит от того, как была собрана библиотека и от того, какие строки (ASCII/Unicode) сравниваются. &lt;/li&gt;    &lt;li&gt;Поддерживаются триггеры, причем можно повесить триггер на обновление какой-то конкретной колонки. &lt;/li&gt;    &lt;li&gt;Параметры могут определяться так же, как и в SQL Server-е - @ParamName (но поддерживаются и другие варианты: ?Name, :Name, $Name). &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-595306181805699938?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/595306181805699938/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/04/sqlite.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/595306181805699938'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/595306181805699938'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/04/sqlite.html' title='Бытовые особенности SQLite'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-345379691071564863</id><published>2010-02-11T23:34:00.001+02:00</published><updated>2010-02-11T23:35:00.090+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='Разное'/><title type='text'>Синхронизация доступа: on Type or not on Type</title><content type='html'>&lt;p&gt;Сегодня на собеседовании задал кандидату вопрос о том, как реализовать Singleton в C#. Сначала он предложил самый простой вариант (не thread safe) и в ходе дальнейшего обсуждения (“а почему так? а что если у нас многопоточное приложение? и что делать в этом случае?” и т.п.) предлагается вариант с блокировкой (внимание):&lt;/p&gt;  &lt;p&gt;…&lt;/p&gt;  &lt;p&gt;lock(typeof(MySingleton)) { …&lt;/p&gt;  &lt;p&gt;В этот момент я подумал: “однако…”. На вопрос: “А почему так?” кандидат ответить затруднился, сославшись на некую статью, читаную им когда-то на просторах интернета.&lt;/p&gt;  &lt;p&gt;На первый взгляд идея выглядит подкупающе свеж: не нужно держать отдельный объект, по которому идет синхронизация, все просто и элегантно. Даже &lt;a href="http://msdn.microsoft.com/en-us/library/system.type.aspx" target="_blank"&gt;MSDN&lt;/a&gt; подтверждает, что объекты класса Type уникальны (т.е. в своем роде синглтоны):&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;A Type object that represents a type is unique; that is, two Type object references refer to the same object if and only if they represent the same type.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Там же сказано, что объекты этого класса являются безопасными с точки зрения многопоточного доступа – ну прямо идеальный кандидат для наших нужд. Но мысль о том, почему же я никогда раньше не слышал о таком варианте, так и не давала покоя. Ответ содержится в той же самой статье:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;    &lt;p&gt;In multithreading scenarios, do not lock Type objects in order to synchronize access to static data. Other code, over which you have no control, might also lock your class type. This might result in a deadlock. Instead, synchronize access to static data by locking a private static object.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Так что “увы и ах” - не судьба (даром что выглядит элегантно). И не забываем о пользе чтения документации.&lt;/p&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-345379691071564863?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/345379691071564863/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/02/on-type-or-not-on-type.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/345379691071564863'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/345379691071564863'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/02/on-type-or-not-on-type.html' title='Синхронизация доступа: on Type or not on Type'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-2876504954920314688</id><published>2010-02-03T23:48:00.001+02:00</published><updated>2010-02-03T23:58:01.336+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><title type='text'>Первые впечатления от StreamInsight (aka Complex Event Processing от Microsoft)</title><content type='html'>&lt;p&gt;В состав SQL Server 2008 R2 релиз которого намечен на май этого года, входит технология StreamInsight (&lt;a href="http://www.microsoft.com/sqlserver/2008/en/us/R2-complex-event.aspx" target="_blank"&gt;официальная страница&lt;/a&gt; и &lt;a href="http://blogs.msdn.com/streaminsight/" target="_blank"&gt;блог&lt;/a&gt;). Несмотря на очевидную ассоциированность, к SQL Server-у эта технология не имеет практически никакого отношения – кроме того факта, что их “положили в одну коробку”.&lt;/p&gt;  &lt;p&gt;StreamInsight – это попытка Microsoft выйти на новый для себя рынок &lt;a href="http://en.wikipedia.org/wiki/Complex_event_processing" target="_blank"&gt;Complex Event Processing&lt;/a&gt; (в конце поста есть несколько ссылок на информацию о других игроках). Общее описание идея очень сильно напомнило мне системы обработки сообщений: скорее всего из-за наличия потоков, очередей, обработки и прочих схожих концепций. Ключевым, на мой взгляд, является слово “Complex”: предполагается, что мы производим над входным потоком событий некоторые нетривиальные манипуляции, которые позволяют нам осуществлять анализ этого потока в реальном времени (точнее “с известной задержкой”, поскольку зачастую речь идет об операциях, проводимых на каком-то [скользящем] окне наблюдений). Технология доступна пока только для предварительно просмотра, скачать можно &lt;a href="http://go.microsoft.com/fwlink/?LinkID=161910" target="_blank"&gt;отсюда&lt;/a&gt;. Дистрибутив компактен (около 10 Мб) и содержит неплохую подборку примеров и обзорную документацию. Последняя, однако, не настолько актуальна, как &lt;a href="http://msdn.microsoft.com/en-us/library/ee362541(SQL.105).aspx" target="_blank"&gt;MSDN&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;В обзорной документации все описано простым и понятным языком:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Определяем входной поток событий.&lt;/li&gt;    &lt;li&gt;Задаем механизм обработки - на самом деле Linq-запрос, в котором можно использовать некоторые специфичные для потоков событий функции (вроде обращения к “окну”).&lt;/li&gt;    &lt;li&gt;Задаем характеристики выходного потока - поток результатов выполнения запроса из п.2 на входном потоке событий.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Обещается масштабируемость “куда хочешь” и параллельная обработка “как хочешь”.&lt;/p&gt;  &lt;p&gt;В ходе попытки самостоятельно реализовать приложение, использующее эту технологию, пришлось столкнуться со следующими проблемами:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Для того, чтобы “воткнуть” поток в CEP Engine (который занимается вычислениями/обработкой) необходимо реализовать InputStreamAdapter (который выполняется в контексте того же процесса, который выполняет CEP Engine). Этот адаптер на самом деле является конечным автоматом (aka &lt;a href="http://en.wikipedia.org/wiki/Finite-state_machine" target="_blank"&gt;State Machine&lt;/a&gt;). Состояния вроде бы &lt;a href="http://msdn.microsoft.com/en-us/library/ee391355(SQL.105).aspx" target="_blank"&gt;описаны&lt;/a&gt;, но машина далеко не так проста, как хотелось бы. Причем со всеми этими переходами приходится быть чрезвычайно аккуратным, ибо в примерах есть грозные предупреждения вида “если не сделать вот этот ‘финт ушами’, то в CEP Engine будет утечка памяти”. Таким образом, для реализации простейшего адаптера приходится писать довольно много не относящегося к делу кода. Аналогичная ситуация и с выходными потоками – тот же конечный автомат и те же проблемы: довольно много сервисного кода на ровном месте.&lt;/li&gt;    &lt;li&gt;Не до конца понятен механизм типизации событий: она (типизация) вроде бы заложена в сам поток (который является generic-классом), но отсутствует в адаптере потока (который, собственно, и “производит” поток для CEP Engine).&lt;/li&gt;    &lt;li&gt;Пока не совсем понятно, как все-таки эта штука масштабируется и какая у нее пропускная способность.&lt;/li&gt;    &lt;li&gt;Также не очень ясно, насколько сложный анализ можно производить: в расчете простейших агрегаций особого (революционного) смысла нет, а вот относительно более сложных расчетов возникают вопросы: как их “скрестить” с Linq? какую величину окна сможет безболезненно (для производительности) сохранять CEP Engine? Ответов я пока не нашел.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Однако “попилим еще” и будет видно. Глядишь, к моменту выхода (или к версии 2 ;-) ) станет более понятно что к чему.&lt;/p&gt;  &lt;p&gt;Где еще можно об этом почитать:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://complexevents.com/"&gt;http://complexevents.com/&lt;/a&gt;;&lt;/li&gt;    &lt;li&gt;Event-driven architecture: &lt;a href="http://en.wikipedia.org/wiki/Event-driven_architecture"&gt;http://en.wikipedia.org/wiki/Event-driven_architecture&lt;/a&gt;;&lt;/li&gt;    &lt;li&gt;Один из вендоров CEP: &lt;a href="http://web.progress.com/en/apama/index.html" target="_blank"&gt;Apama&lt;/a&gt;;&lt;/li&gt;    &lt;li&gt;Еще один вендор CEP: &lt;a href="http://www.streambase.com/" target="_blank"&gt;StreamBase&lt;/a&gt;, в википедии о них же есть довольно &lt;a href="http://en.wikipedia.org/wiki/StreamBase_Systems" target="_blank"&gt;любопытная статья&lt;/a&gt;.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-2876504954920314688?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/2876504954920314688/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/02/streaminsight-aka-complex-event.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/2876504954920314688'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/2876504954920314688'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/02/streaminsight-aka-complex-event.html' title='Первые впечатления от StreamInsight (aka Complex Event Processing от Microsoft)'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-1821161034034038652</id><published>2010-01-11T09:00:00.001+02:00</published><updated>2010-01-21T22:39:38.362+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><title type='text'>SqlDependency: размножение дикобразов</title><content type='html'>&lt;blockquote&gt;   &lt;p&gt;Как размножаются дикобразы?&lt;/p&gt;    &lt;p&gt;Очень, очень осторожно либо безрезультатно – что-то дикобразов нигде не видно.&lt;/p&gt;    &lt;p&gt;(С) “Афера Томаса Крауна”&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;В связи с переходом на новую работу, на мне “подвисла” одна задача, которую я не успел решить в старом проекте. Связана она была, как нетрудно догадаться из темы, с SqlDependency. Дело получается относительно давнее (вот уже больше двух месяцев как я в этом проекте не участвую), но поскольку недавно стало известно, что проблема решена, я&amp;#160; ставлю “зарубку на память”.&lt;/p&gt;  &lt;p&gt;Вкратце предыстория такова: в какой-то момент времени было принято решение использовать SqlDependency для очистки кэша настроек приложения – сценарий достаточно стандартный. Как всегда нестандартными (на первый взгляд) были проблемы, возникшие позже. Настройки хранятся в специальной БД (ConfigDB), к которой обращаются приложения (ASP.NET App, несколько экземпляров потенциально на разных серверах) и [вспомогательные] “сервисы” в виде служб и консольных приложений, запускаемых по расписанию. &lt;/p&gt;  &lt;p&gt;В какой-то момент с ConfigDB начинаются проблемы:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;невозможно сохранить измененные настройки (timeout) &lt;/li&gt;    &lt;li&gt;измененные настройки не подхватываются клиентами &lt;/li&gt;    &lt;li&gt;SqlServer начинает ощутимо тормозить (хотя поначалу это не связывается с ConfigDB) &lt;/li&gt;    &lt;li&gt;журнал ошибок SqlServer-а забивается сообщениями с “жалобами” на query norifications (“The query notifications dialog conversation handle ‘{xxxxxx….}’ closed due to the following error …”, with error code 8490 or 8470) &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Проблема живет только на продакшине и воспроизвести ее в тестовом окружении не удается. Попытки хоть как-то локализовать проблему дают следующий результат:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;создание и использование новой ConfigDB помогает, правда ненадолго – через пару дней/неделю все возвращается на круги своя &lt;/li&gt;    &lt;li&gt;восстановление ConfigDB из резервной копии занимает очень много времени – база объемом 500 Мб (из которых 400 – это журнал транзакций) при восстановлении на двухьядерной машине с 2 Гб оперативной памяти намертво подвешивает ее (процесс не завершился за 1.5 часа) &lt;/li&gt;    &lt;li&gt;выясняется, что в ConfigDB очень много открытых подписок (query notification) и их количество растет &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Проблему в итоге удалось устранить путем:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Вдумчивого чтения и переработки кода, который работает с объектами SqlDependency. &lt;/li&gt;    &lt;li&gt;Закрытия всех открытых подписок (об этом ниже). &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;По ходу работы пару раз слышались голоса “SqlDependency – это зло”, “и зачем вы вообще это используете”, “да никогда и ни в жизни не пользовать ЭТО”, “лучше сделать все ‘руками’”. В итоге скептики были посрамлены, механизм работает как и ожидалось, проблему устранили. Но должен признать, что работать с SqlDependency необходимо очень осторожно и внимательно.&lt;/p&gt;  &lt;p&gt;Итак, о чем нужно помнить при использовании этой технологии:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Строго следите за тем, чтобы на каждый вызов SqlDependency.Start приходился вызов SqlDependency.Stop – об этом явно написано в MSDN, но на моих глазах (в 2х случаях из 2х) об этом “забывали”, а потом долго искали решение возникших проблем. Хорошим решением будет обернуть всю работу с SqlDependency в некий класс (отвечающий за кеширование/инвалидацию), реализаций IDisposable, и помещение вызовов Start и Stop в конструктор и Dispose соответственно. &lt;/li&gt;    &lt;li&gt;В обработчике события SqlDependency.OnChange не ленитесь анализировать &lt;strong&gt;&lt;em&gt;причину&lt;/em&gt;&lt;/strong&gt; вызова обработчика (SqlNotificationEventArgs.Info). Игнорирование этого простого совета может привести (и рано или поздно приводит) к возникновению эффекта, похожего на &amp;quot;положительную обратную связь”: когда на сервере что-то пошло не так, вызвался обработчик, мы тут же создали новую подписку, добавив тем самым проблем серверу, который тут же снова вызовет наш обработчик. &lt;/li&gt;    &lt;li&gt;Не забывайте о протоколировании – наличие информации о том, когда и с какими параметрами вызывается обработчик события OnChange здорово упрощает диагностику. &lt;/li&gt;    &lt;li&gt;Проводите мониторинг SqlServer-а. Отслеживание количества активных подписок (select count(*) from sys.dm_qn_subscriptions) позволит вам на самых ранних этапах обнаружить “утечку” и устранить ее пока это не переросло в БОЛЬШУЮ проблему. Обычно именно мониторингом все склонны пренебрегать. &lt;/li&gt;    &lt;li&gt;Считайте количество подписок, которые будут регистрироваться на сервере в ходе нормального (штатного) функционирования вашего приложения. Десятки подписок – не проблема. При сотнях стоит уже быть ооочень осторожным. Если же расчетное количество подписок исчисляется тысячами, то следует подумать о изменении архитектуры с целью уменьшения этого числа. &lt;/li&gt;    &lt;li&gt;Не самой лучше идеей будет [активно] использовать этот механизм в базе данных с высокой нагрузкой. &lt;/li&gt;    &lt;li&gt;Если что-то пошло не так, помним о возможности “убить” подписку: KILL QUERY NOTIFICATION SUBSCRIPTION &amp;lt;subscription id/ALL&amp;gt;. &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;UPDATE: как было совершенно справедливо замечено Airex-ом, при использовании SqlDependency в контексте ASP.NET, наилучшим местом для вызова SqlDependency.Stop является Application_End (в Global.asax). И не стоит надеяться на деструктор того класса, в который вы “запрячете” всю работу с SqlDependency – в этой связи немного обновил (уточнил) первый пункт выше.&lt;/p&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-1821161034034038652?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/1821161034034038652/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/01/sqldependency.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1821161034034038652'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1821161034034038652'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/01/sqldependency.html' title='SqlDependency: размножение дикобразов'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-7240005120922695825</id><published>2010-01-05T20:01:00.001+02:00</published><updated>2010-01-05T20:22:55.410+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='Performance'/><title type='text'>Enum.Parse vs string comparison</title><content type='html'>&lt;p&gt;В ходе code review столкнулся с подобным кодом:&lt;/p&gt;  &lt;p&gt;if ((SomeEnum)Enum.Parse(typeof(SomeEnum), value) == SomeEnum.SomeValue)    &lt;br /&gt;{     &lt;br /&gt;// делается что-то полезное     &lt;br /&gt;}&lt;/p&gt;  &lt;p&gt;И так много раз подряд. Выглядит громоздко и не слишком элегантно. К тому же меня стало терзать сомнение что Enum.Parse – не самый быстрый метод. Первой мыслью было заменить на что-нибудь вроде такого:&lt;/p&gt;  &lt;p&gt;if (value == SomeEnum.SomeValue.ToString())    &lt;br /&gt;{     &lt;br /&gt;// делается что-то полезное     &lt;br /&gt;}&lt;/p&gt;  &lt;p&gt;Но потом решил проверить. Так вот, первый вариант работает в 3-4 раза быстрее второго. Использование Reflector-а позволяет узнать, что внутри Enum.Parse кэшируются значения перечислений и в результате со второго вызова вся эта операция сводится к поиску значения в хэш-таблице. Преобразование элемента перечисления к строке (второй вариант) оказывается гораздо сложнее и использует рефлексию.    &lt;br /&gt;Ситуация, однако, кардинально меняется, если value содержит значение, которому нет соответствия SomeEnum. При этом выбрасывается исключение (которое в оригинальном коде никак не обрабатывается) и выполнение кода существенно замедляется – в 30 раз по сравнению с вариантом №2.     &lt;br /&gt;Резюме: если вы уверены в том, что в подавляющем большинстве случаев к вам придет допустимое значение, то используйте Enum.Parse - думаю 3-4х кратный прирост производительности компенсирует некоторое ухудшение читаемости кода.&lt;/p&gt;  &lt;p&gt;HTH,    &lt;br /&gt;AlexS     &lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-7240005120922695825?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/7240005120922695825/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2010/01/enumparse-vs-string-comparison.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/7240005120922695825'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/7240005120922695825'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2010/01/enumparse-vs-string-comparison.html' title='Enum.Parse vs string comparison'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-9144569987319797576</id><published>2009-10-31T12:32:00.001+02:00</published><updated>2009-10-31T17:40:35.265+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Разное'/><title type='text'>Dev magic и прочий ooops</title><content type='html'>&lt;p&gt;На днях коллега по предыдущему проекту (привет Серега!) занялся улучшением одной из утилит, которую мы с ним разрабатывали. В тот день его посетило вдохновение (что, надо сказать, случается нередко) и он реализовал пару-тройку финтифлюшек вроде отображения состояния рабочих процессов (функциональности, правда, тоже ощутимо прибавилось). Однако делать некоторые телодвижения разработчикам невообразимо скучно (что я отлично понимаю), поэтому в состоянии отображается не &amp;quot;пользовательская&amp;quot; строка, а то, что можно получить &amp;quot;здесь и сейчас без особых заморочек&amp;quot; (благо код пишется так, чтобы читать его можно было без комментариев). Теперь это чудо инженерной мысли выглядит примерно следующим образом&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh4.ggpht.com/_0v8-WQDReFE/SuwSK2YIgnI/AAAAAAAAFPk/-SP-4z-Ar8M/s1600-h/DevMagic%5B5%5D.png"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="439" alt="DevMagic" src="http://lh3.ggpht.com/_0v8-WQDReFE/SuwSMH1SPSI/AAAAAAAAFPo/QhHBi-nrWXQ/DevMagic_thumb%5B3%5D.png?imgmax=800" width="373" border="0" /&gt;&lt;/a&gt;&lt;/p&gt;  &lt;p&gt;Как может заключить &amp;quot;простой пользователь&amp;quot; взглянув на это окно, магии сделано уже 4655 из потенциальных 31820. Типа &amp;quot;побольше магии хорошей и разной&amp;quot;. Что за магия и к чему она применяется - непонятно. Впрочем, может оно и к лучшему - меньше поводов волноваться.&lt;/p&gt;  &lt;p&gt;Конечно же это не более чем баловство (от которого именно в этом конкретном случае можно, и даже нужно, было бы отказаться), но оно поднимает более важный вопрос об отношении к тому, что ты делаешь и как. На эту тему интересно &lt;a href="http://www.codinghorror.com/blog/archives/001238.html" target="_blank"&gt;высказался&lt;/a&gt; &lt;a href="http://www.codinghorror.com/blog/" target="_blank"&gt;Jeff Atwood&lt;/a&gt; в своем посте о сообщениях об ошибках в Google Chrome, и я его поддерживаю - серьезное и вдумчивое отношение к продукту своей работы совершенно не обязательно должно выражаться в стандартизированной скуке всего и вся.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-9144569987319797576?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/9144569987319797576/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/10/dev-magic-ooops.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/9144569987319797576'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/9144569987319797576'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/10/dev-magic-ooops.html' title='Dev magic и прочий ooops'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh3.ggpht.com/_0v8-WQDReFE/SuwSMH1SPSI/AAAAAAAAFPo/QhHBi-nrWXQ/s72-c/DevMagic_thumb%5B3%5D.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-5287302103477493466</id><published>2009-10-18T21:30:00.001+03:00</published><updated>2009-10-18T21:41:25.689+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='Поделки'/><category scheme='http://www.blogger.com/atom/ns#' term='Разное'/><title type='text'>Объединение истории сообщений Skype</title><content type='html'>&lt;p&gt;В последние полтора года я стал достаточно активно использовать Skype в качестве IM-клиента. Причем корпоративная политика отразилась и на персональных предпочтениях, ведь привычка - вторая натура. Тем более, что начиная с версии 4 Skype стал достаточно удобным в повседневном использовании (до этого я очень плохо с ним уживался).&lt;/p&gt;  &lt;p&gt;В связи со сменой работы возник вопрос синхронизации истории сообщений между профилем, который я забрал с работы и личным профилем, который хранился дома. Ну или экспорт/импорт (в крайнем случае). Поясню: я использую одину и ту же учетную запись дома и на работе. Частенько получается, что начинаешь разговор с кем-то с работы, а заканчиваешь его дома. В результате история сообщений разорвана между двумя (а то и больше) компьютерами. Гугл мало чем смог помочь: есть плагин к Skype, который автоматом сохраняет все звонки и исообщения в гуглопочте - неплохо, конечно, но во-первых избыточно, во-вторых за деньги ($25 может и немного, но нет, спасибо). Еще на форуме Skype можно найти пару ссылок на программы, которые могут вытащить историю и выгрузить ее в txt или html. Тоже не совсем то. Пришлось разбираться самому.&lt;/p&gt;  &lt;p&gt;На проверку все оказалось не так плохо (хотя и несколько запутано):&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;для хранения профиля (в том числе и истории звонков и сообщений) Skype использует базу данных &lt;a href="http://www.sqlite.org/" target="_blank"&gt;SQLite&lt;/a&gt; - что хорошо (ибо эта СУБД открытая и для нее имеется &lt;a href="http://sqlite.phxsoftware.com/" target="_blank"&gt;.NET Provider&lt;/a&gt;)&lt;/li&gt;    &lt;li&gt;схема не то чтобы сложная (16 таблиц), но о связях приходится догадываться (ибо внешние ключи не определены) и таблицы довольно &amp;quot;широкие&amp;quot; (до 40 полей)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;За выходные в неспешном режиме написал программулину, которая делает то, что нужно (ну или почти): копирует сообщения из одного профиля в другой (если их там еще нет). Выглядит сиё чудо инженерной мысли следующим образом:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_0v8-WQDReFE/StthUOpllZI/AAAAAAAAFOc/7eOY02e9rsM/s1600-h/image%5B7%5D.png"&gt;&lt;img style="border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px" height="169" alt="image" src="http://lh5.ggpht.com/_0v8-WQDReFE/StthVDu9r3I/AAAAAAAAFOg/02NqMSqfNm8/image_thumb%5B3%5D.png?imgmax=800" width="427" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Скачать его можно &lt;a href="http://cid-882d5c8073a0d229.skydrive.live.com/self.aspx/Public/SkypeChatHistorySync.exe" target="_blank"&gt;отсюда&lt;/a&gt; (выложил на SkyDrive на случае если кому еще пригодится).&lt;/p&gt;  &lt;p&gt;Что следует иметь в виду, в ходе синхронизации:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;профили должны быть от одной версии Skype (строго, ибо даже между v.4.0 и v.4.1 схема имеет некоторые различия)&lt;/li&gt;    &lt;li&gt;профиль хранится в каталоге %user dir%\AppData\Roaming\Skype\&amp;lt;your skype name&amp;gt;\main.db&lt;/li&gt;    &lt;li&gt;перед внесением изменений в целевой профиль, делается его резервная копия (так что в случае чего его можно вернуть)&lt;/li&gt;    &lt;li&gt;свойства чата не обновляются - так что в списке &amp;quot;последних разговоров&amp;quot; можно не увидеть действительно последние разговоры, которые были добавлены в ходе синхронизации (но в базе они будут)&lt;/li&gt;    &lt;li&gt;иногда происходит дублирование сообщений (не всегда) - я так и не смог надежно определить условия, при которых это происходит&lt;/li&gt;    &lt;li&gt;в некоторых случаях в истории появляется сообщение &amp;quot;это сообщение было удалено из чата&amp;quot; - тоже не смог понять из-за чего (подозреваю что где-то не хватате связанной записи ... но вот где?)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Так что не все так шоколадно, как хотелось бы. Но это в любом случае лучше, чем ничего. Тем более, что теперь я знаю, как сделать экспорт истории (не только сообщений, но и звонков, и контактов) почти в любой формат ;-)&lt;/p&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-5287302103477493466?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/5287302103477493466/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/10/skype.html#comment-form' title='Комментарии: 8'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/5287302103477493466'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/5287302103477493466'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/10/skype.html' title='Объединение истории сообщений Skype'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_0v8-WQDReFE/StthVDu9r3I/AAAAAAAAFOg/02NqMSqfNm8/s72-c/image_thumb%5B3%5D.png?imgmax=800' height='72' width='72'/><thr:total>8</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-8188907438744626524</id><published>2009-09-28T22:48:00.001+03:00</published><updated>2009-09-28T23:03:57.957+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='.NET'/><title type='text'>Неочевидный redirect</title><content type='html'>&lt;p&gt;На днях коллега ((C) Серега К.) натолкнулся на такую любопытную особенность HttpResponse.Redirect. Предположим, что у нас где-то в коде страницы или компонента ASP.NET есть такой-вот код, вполне на первый взгляд логичный, и не вызывающий бурю протеста:&lt;/p&gt;  &lt;p&gt;try&lt;/p&gt;  &lt;p&gt;{&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // что-то делаем&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Response.Redirect(&amp;quot;url1&amp;quot;);&lt;/p&gt;  &lt;p&gt;}&lt;/p&gt;  &lt;p&gt;catch (Exception ex)&lt;/p&gt;  &lt;p&gt;{&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; // Обрабатываем все на свете&lt;/p&gt;  &lt;p&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160; Response.Redirect(&amp;quot;url2&amp;quot;);&lt;/p&gt;  &lt;p&gt;}&lt;/p&gt;  &lt;p&gt;А теперь вопрос: куда будет перенаправлен запрос в результате &lt;strong&gt;&lt;em&gt;нормального&lt;/em&gt;&lt;/strong&gt; (т.е. без исключений) выполнеия кода? url1? А вот и нет - на самом деле url2. Все дело в том, что внутри HttpResponse.Redirect для прекращения обработки запроса вызывается Thread.CurrentThread.Abort. Таким образом наш обработчик &amp;quot;всего на свете&amp;quot;, отловит это исключение и перенаправим запрос на url2.&lt;/p&gt;  &lt;p&gt;Для того, чтобы избежать подобных неприятностей, необходимо использовать другую версию Redirect - с двумя аргументами:&lt;/p&gt;  &lt;p&gt;Redirect(string url, bool endResponse)&lt;/p&gt;  &lt;p&gt;второй аргумент следует установить в false.&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;HTH&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-8188907438744626524?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/8188907438744626524/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/09/redirect.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8188907438744626524'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8188907438744626524'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/09/redirect.html' title='Неочевидный redirect'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-1047147229153131264</id><published>2009-08-08T00:43:00.001+03:00</published><updated>2009-08-08T00:43:16.261+03:00</updated><title type='text'>За что я не люблю визуальные дизайнеры и case-средства для разработки БД</title><content type='html'>&lt;ol&gt;   &lt;li&gt;&lt;strong&gt;&lt;em&gt;Не дают полного контроля над тем, что происходит с БД&lt;/em&gt;&lt;/strong&gt;.      &lt;br /&gt;Особенно это неприятно/опасно в случае, когда с помощью визуального дизайнера редактируется таблица, в которой есть данные - во многих случаях изменения потребуют пересоздания таблицы. Да, SQL Server Management Studio (в частности) во-первых будет сохранять данные, а во-вторых по-умолчанию запрещает вносить подобные изменения, но все-равно ... я предпочитаю вносить подобные изменения сознательно и сохранить соответствующий скрипт &amp;quot;для будущих поколений&amp;quot; (ибо он наверняка понадобится).&lt;/li&gt;    &lt;li&gt;&lt;strong&gt;&lt;em&gt;Задают (слишком) много параметров &amp;quot;по-умолчанию&amp;quot;&lt;/em&gt;&lt;/strong&gt;.      &lt;br /&gt;Чем больше таких параметров - тем больше вероятность забыть о них, что чревато проблемами. А чем позднее мы обнаруживаем эти проблемы - тем дороже стоит их решение.&lt;/li&gt;    &lt;li&gt;&lt;strong&gt;&lt;em&gt;Не слишком-то экономят время&lt;/em&gt;&lt;/strong&gt;.      &lt;br /&gt;Нечасто приходится разрабатывать схему большой БД &amp;quot;с нуля&amp;quot;. И тем более, это никогда не пироисходит &amp;quot;все и сразу&amp;quot; - схема появляется постепенно. А коли так, то я быстрее напишу SQL код &amp;quot;вручную&amp;quot;, чем &amp;quot;дизайнер + генерация + доработка напильником&amp;quot;. Если мне нужна картинка, то reverse engineering по уже созданной (из скриптов) базе никто не отменял.&lt;/li&gt;    &lt;li&gt;&lt;strong&gt;&lt;em&gt;Не способствуют изучению SQL&lt;/em&gt;&lt;/strong&gt;.      &lt;br /&gt;Это, конечно, так себе аргумент, но тем не менее - знание SQL еще никому не мешало, а использование дизайнеров развращает.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-1047147229153131264?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/1047147229153131264/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/08/case.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1047147229153131264'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1047147229153131264'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/08/case.html' title='За что я не люблю визуальные дизайнеры и case-средства для разработки БД'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-1461892610047813844</id><published>2009-07-06T23:53:00.001+03:00</published><updated>2009-07-06T23:53:18.492+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><title type='text'>Журнал транзакций и запросы на выборку данных</title><content type='html'>&lt;p&gt;Решил разобраться в том, что же на самом деле представляет из себя журнал транзакций (transaction log) в SQL Server-е. &lt;/p&gt;  &lt;p&gt;Весь Books Online/MSDN пронизан мыслью о том, что журнал транзакций - это жизненно важный компонент базы данны и содержит данные о всех транзакциях, которые в ней выполняются. Эта мысль понятна, но меня долгое время занимал вопрос: как же быть с запросами на выборку данных? Они ведь тоже имеют свой уровень изоляции и выполняются в транзакции (пусть и неявной). Записываются ли они в журнал? И если да, то в каком виде и зачем?&lt;/p&gt;  &lt;p&gt;Провел пару экспериментов, посмотрел сам журнал и выяснилось: в журнал транзакций не записываются &amp;quot;нерезультативные&amp;quot; тразакции (те, тразакции, которые не приводят к изменению данных). Перефразирую: журнал транзакций содержит &amp;quot;изменения, вносимые в БД&amp;quot;, а не &amp;quot;запросы, выполняемые к БД&amp;quot;.&lt;/p&gt;  &lt;p&gt;Ниже приведу факты, которые (на мой взгляд) существенны для понимания того, чем является и чем &lt;strong&gt;&lt;em&gt;не&lt;/em&gt;&lt;/strong&gt; является журнал тразакций в SQL Server:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Запись в журнал производится ДО того, как происходит изменение данных. &lt;/li&gt;    &lt;li&gt;Каждая запись содержит идентификатор транзакции, в рамках которой производится данное изменение - это позволяет откатить или заново выполнить любую запротоколированную транзакцию. &lt;/li&gt;    &lt;li&gt;Все записи в журнале (и, соответственно, все действия, производимые на физическом увроне с БД) делятся на две категории: те, для отката/повторения которых записывается логическая операция и те, для отката/повторения которых записывается образ данных до и после выполенния операции.&lt;/li&gt;    &lt;li&gt;Протоколируются только действия, приводящие к изменению данных - т.е. фактически &lt;strong&gt;&lt;em&gt;журнал транзакций содержит результат обработки входящего потока транзакций, а не сами транзакции&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-1461892610047813844?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/1461892610047813844/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/07/blog-post_06.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1461892610047813844'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1461892610047813844'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/07/blog-post_06.html' title='Журнал транзакций и запросы на выборку данных'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-5771093630316088909</id><published>2009-07-02T23:26:00.002+03:00</published><updated>2009-07-03T15:37:12.438+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Tools'/><category scheme='http://www.blogger.com/atom/ns#' term='Разное'/><title type='text'>Забавные картинки</title><content type='html'>&lt;p&gt;Недавно разжился лицензией на &lt;a href="http://www.red-gate.com/products/SQL_Professional_Toolbelt/index.htm" target="_blank"&gt;Red-Gate SQL Toolbelt&lt;/a&gt;. Среди прочего, в него входит инструмент с незатейливым названием SQL Dependency Tracker. Название меня особенно не впечатлило, казалось: ну что нового можно сказать о взаимозависимостях объектов в базе данных (хоть sp_MSdependencies и является недокументированной, но используется довольно широко)? Но из любопытства решил посмотреть.&lt;/p&gt;  &lt;p&gt;И вот оно: схема зависимостей между объектами в базе данных нашего проекта.&lt;/p&gt;  &lt;p&gt; &lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh5.ggpht.com/_0v8-WQDReFE/Sk0X_kktBmI/AAAAAAAAEPQ/FrErKhuwM0k/s1600-h/ClientDatabaseDiagram3.png"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="358" alt="ClientDatabaseDiagram" src="http://lh4.ggpht.com/_0v8-WQDReFE/Sk0YA9vvJDI/AAAAAAAAEPU/qXhA9GAFjtE/ClientDatabaseDiagram_thumb1.png?imgmax=800" width="364" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Вроде бы банальная штука, зато как смотрится! Ну просто форменный computer art! И главное: одного взгляда достаточно для того, чтобы понять вокруг чего крутятся колеса системы.&lt;/p&gt;  &lt;p&gt;За всеми "профессиональными"/минималистическими рабочими привычками вроде набора кода вслепую, консолей, запросов, комбинаций клавиш и прочего, как-то забывается великая сила толковой визуализации ... хорошо когда попадается что-то, о ней напоминающее :-)&lt;/p&gt;  &lt;p&gt;HTH&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-5771093630316088909?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/5771093630316088909/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/07/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/5771093630316088909'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/5771093630316088909'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/07/blog-post.html' title='Забавные картинки'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_0v8-WQDReFE/Sk0YA9vvJDI/AAAAAAAAEPU/qXhA9GAFjtE/s72-c/ClientDatabaseDiagram_thumb1.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-1328964546735626328</id><published>2009-06-24T23:10:00.001+03:00</published><updated>2009-07-06T23:56:47.674+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>T-SQL Simple Facts</title><content type='html'>&lt;p&gt;В последние пару недель читал блоги/Books Online и (заново) открыл для себя некоторые &amp;quot;простые&amp;quot; факты, некоторые из которых здорово меня удивили (признаюсь). Некоторые другие всплыли &amp;quot;по ассоциации&amp;quot;, в итоге решил поделиться всеми сразу:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Всем хорошо известен оператор GO, означающий окончание пакета. Так вот, полный синтаксит этого оператора: GO [count] - где count - положительное целое число, указывающее, что предшествующий пакет необходимо выполнить count раз (квадратные скобки указывают, что этот параметр может отсутствовать (что и происходит в ошеломляющем большинстве случаев)). &lt;/li&gt;    &lt;li&gt;Для типа данных datetime определена операция сложения, причем вторым аргументом может быть целое число, которое интерпретируется как количество дней. Так:      &lt;br /&gt;DECLARE @D datetime       &lt;br /&gt;SET @D = '2009/06/24 15:00'       &lt;br /&gt;SET @D = @D + 2       &lt;br /&gt;PRINT @D       &lt;br /&gt;выдаст 26 июня 2009, 15:00 ... когда я увидел эту запись, то признаться сильно удивился. &lt;/li&gt;    &lt;li&gt;Для добавления данных в таблицу можно использовать следующий синтаксис:      &lt;br /&gt;INSERT INTO &amp;lt;Имя таблицы&amp;gt; DEFAULT VALUES;       &lt;br /&gt;Сработает, конечно, только в том случае, когда для всех полей таблицы, не считая автоинкрементных и timestamp, определены значения по-умолчанию, но все-равно любопытно. &lt;/li&gt;    &lt;li&gt;Тип данных timestamp не имеет ничего общего с *nix timestamp (отсчитывающего количество миллисекунд с начала Unix-эры). В SQL Server колонки этого типа данных обновляются автоматически при любых операциях добавления/изменения записи - т.е. это некий аналог &amp;quot;версии строки&amp;quot;, за который отвечает сам сервер. Гарантируется уникальность значений полей timestamp в рамках каждой базы данных. &lt;/li&gt;    &lt;li&gt;Переменные не покрываются транзакцией:      &lt;br /&gt;DECLARE @i       &lt;br /&gt;SET @i = 10       &lt;br /&gt;BEGIN TRANSACTION       &lt;br /&gt;SET @i = 20       &lt;br /&gt;INSERT INTO SomeTable VALUES (@i)       &lt;br /&gt;ROLLBACK TRANSACTION       &lt;br /&gt;PRINT @i       &lt;br /&gt;напечатает 20 (а не 10, как можно было бы ожидать). &lt;/li&gt;    &lt;li&gt;Синтаксис ALTER TABLE ... ALTER позволяет изменить определение поля таблицы но не его имя (что можно было бы ожидать). Для переименования необходимо воспользоваться хранимой процедурой sp_rename (не слишком элегантно, но действенно). &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;HTH,&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-1328964546735626328?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/1328964546735626328/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/06/t-sql-simple-facts.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1328964546735626328'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1328964546735626328'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/06/t-sql-simple-facts.html' title='T-SQL Simple Facts'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-245360500071586217</id><published>2009-05-12T23:53:00.001+03:00</published><updated>2009-05-12T23:57:05.666+03:00</updated><title type='text'>Что такое OPENQUERY и чем оно может нам помочь</title><content type='html'>&lt;p&gt;На днях мне задали задачку (перефразируя): &lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Есть сервера A, B и С. Сервер A связан с сервером B (т.е. A доступен с B). Сервер B связан с сервером C (т.е. сервер B доступен с C). При этом в соответствии с политикой безопасности сервера A и C &amp;quot;не видят друг-друга&amp;quot; (т.е. связи между ними нет). При этом на сервере A есть некоторые данные, которые нужно получить/обработать в ходе выполнения задания на сервере C. Можно ли такое организовать?&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;Перефразирую: можно ли каким-либо образом из T-SQL кода открыть соединение к связанному серверу и выполнить какой-то SQL код удаленно? Т.е. в нашем случае: сделать на сервере C что-нибудь такое, чтобы некий SQL код на самом деле выполнился на сервере B (откуда есть доступ к серверу A), а результат вернул нам?&lt;/p&gt;  &lt;p&gt;Ответ: можно (только осторожно). Ключом в данном случае является та самая функция &lt;a href="http://msdn.microsoft.com/en-us/library/ms188427.aspx" target="_blank"&gt;OPENQUERY&lt;/a&gt;, которая вынесена в заголовок.&lt;/p&gt;  &lt;p&gt;Эта функция позволяет выполнить некоторый запрос на связанном сервере (linked server) и использовать его результаты как обычную таблицу:&lt;/p&gt;  &lt;p&gt;&lt;font face="Courier New"&gt;OPENQUERY (linked_server, 'query')&lt;/font&gt;&lt;/p&gt;  &lt;p&gt;Например:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;&lt;font face="Courier New"&gt;SELECT *&lt;/font&gt;&lt;/p&gt;   &lt;font face="Courier New"&gt;     &lt;p&gt;&lt;font face="Courier New"&gt;&lt;/font&gt;&lt;font face="Courier New"&gt;FROM&lt;/font&gt;&lt;/p&gt;   &lt;/font&gt;&lt;font face="Courier New"&gt;     &lt;p&gt;&lt;font face="Courier New"&gt;&lt;/font&gt;&lt;font face="Courier New"&gt;&amp;#160; Table1 t1&lt;/font&gt;&lt;/p&gt;   &lt;/font&gt;&lt;font face="Courier New"&gt;     &lt;p&gt;&lt;font face="Courier New"&gt;&amp;#160; &lt;/font&gt;&lt;font face="Courier New"&gt;LEFT JOIN OPENQUERY([ServerB], 'SELECT * FROM [ServerA].dbname.dbo.SomeTable') q1 ON q1.ID = t1.ID&lt;/font&gt;&lt;/p&gt;   &lt;/font&gt;&lt;/blockquote&gt;  &lt;p&gt;Вот еще некоторые факты об OPENQUERY:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;не допускается передача переменных в качестве аргументов&lt;/li&gt;    &lt;li&gt;допускается использование не только в запросах на выборку данных, но и в запросах на добавление, обновление и удаление&lt;/li&gt;    &lt;li&gt;не может использоваться для выполнения расширенных хранимых процедур&lt;/li&gt;    &lt;li&gt;в случае невнимательного отношения к конфигурированию связанных серверов способна проделать здоровенную дыру в безопасности (как в случае с задачкой выше)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;HTH   &lt;br /&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-245360500071586217?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/245360500071586217/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/05/openquery.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/245360500071586217'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/245360500071586217'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/05/openquery.html' title='Что такое OPENQUERY и чем оно может нам помочь'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-1448127549449272454</id><published>2009-04-20T22:35:00.001+03:00</published><updated>2009-04-20T22:35:30.007+03:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><title type='text'>Полнотекстовый поиск: MS Full-Text Search</title><content type='html'>&lt;p&gt;Вторая часть серии о полнотекстовом поиске несколько задержалась - работа не давала продохнуть. Впрочем, это лирика, приступим к делу.&lt;/p&gt;  &lt;p&gt;Итак, я не буду повторять массу открыто доступной информации о Microsoft Full-Text Search (общее описание от Microsoft (&lt;a href="http://technet.microsoft.com/en-us/library/cc723263.aspx" target="_blank"&gt;eng&lt;/a&gt;/&lt;a href="http://technet.microsoft.com/ru-ru/library/ms142547(SQL.90).aspx" target="_blank"&gt;рус&lt;/a&gt;) и &lt;a href="http://en.wikipedia.org/wiki/SQL_Server_Full_Text_Search" target="_blank"&gt;Wikipedia&lt;/a&gt;, &lt;a href="http://msdn.microsoft.com/en-us/library/ms142541.aspx" target="_blank"&gt;архитектура&lt;/a&gt;), не буду и переписывать простые и не очень примеры. Сосредоточимся на следующем сценарии: у нас имеется некоторое не слишком сложное приложение (веб-приложение, два-три слоя, до несколько Гб данных (до десятка-другого), пара-тройка вспомогательных сервисов) и нам необходимо обеспечить &amp;quot;интеллектуальный поиск&amp;quot; для некоторых сущностей этого приложения. &amp;quot;Интеллектуальность&amp;quot; поиска - маркетинговый прием, которым sales/accounts привлекают пользователей. С чисто технической точки зрения за ним может скрываться следующее:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;находить не только точные вхождения слов из поискового запроса, но и словоформ (мама/маме/мамы/маму ...) &lt;/li&gt;    &lt;li&gt;находить не только слова, введенные пользователем, но и синонимы &lt;/li&gt;    &lt;li&gt;ранжировать результаты поиска по релевантности &lt;/li&gt;    &lt;li&gt;искать не только по атрибутам сущности, но и в теле файла, который с этой сущностью связан &lt;/li&gt;    &lt;li&gt;выполнение сложных запросов (вроде &amp;quot;чтобы вот эти два термина находились поблизости&amp;quot;, задание весов для слов в поисковом запросе и &lt;a href="http://technet.microsoft.com/ru-ru/library/ms142494(SQL.90).aspx" target="_blank"&gt;много чего еще&lt;/a&gt;) &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Из описания можно заключить что скорее всего этот функционал все-таки в большей степени ориентирован на конечного пользователя (и скорее всего &amp;quot;продвинутого пользователя&amp;quot;) и врядли будет использоваться в каких-то вспомогательных сервисах (хотя как знать ...). Теперь рассмотрим насколько сложно/просто будет все это сделать и отгрузить заказчику. Обратимся к следующим основным вопросам: изменения в архитектуре приложения, поддержка/сопровождение и альтернативы.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Архитектура &lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Здесь Microsoft потрудилась на славу: технология достается нам бесплатно не только в смысле денег (идет в комплекте с SQL Server и есть даже у SQL Server Express (with Advanced Services)) но и в смысле интеграции. Вся сложность ложится на плечи SQL Server-а:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Создаем полнотекстовый каталог &lt;/li&gt;    &lt;li&gt;Создаем полнотекстовый индекс &lt;/li&gt;    &lt;li&gt;Используем специальные функции (CONTAINS, CONTAINSTABLE, FREETEXT, FREETEXTTABLE) для обращения к индексам из T-SQL кода &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Таким образом, код приложения может быть полностью абстрагирован от того, каким образом получены данные: с применением полнотекстового поиска или без него. В обмен на эту простоту на стороне СУБД нас поджидает:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Необходимость проектирования физического уровня: сколько каталогов нам нужно и где они будут храниться &lt;/li&gt;    &lt;li&gt;Логический уровень: какие индексы нам нужны, какие колонки индексировать &lt;/li&gt;    &lt;li&gt;Подробности алгоритмов обработки данных: стоп-список (noise words (в SQL Server 2005 - фиксированный, один на сервер, в SQL Server 2008 можно создавать пользовательские)), алгоритм разбиения на слова (word breaker), выделение словоформ (stammer), фильтрация содержимого (content filters) &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Первые два уровня для начала/в несложных случаях, пожалуй, можно отдать на откуп SQL Server-у, а вот третий пункт таит в себе массу подводных камней, которые могут значительно усложнить жизнь &amp;quot;среднестатистическому разработчику&amp;quot;:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;стоп-список: слова, входящие в него, попросту игнорируются и не включаются в индекс (поэтому неудивительно, что найти их тоже не получится) &lt;/li&gt;    &lt;li&gt;разбиение на слова: зависит от выбранного языка (локали), может быть нейтральным (по пробелам/знакам препинания) &lt;/li&gt;    &lt;li&gt;выделение словоформ: зависит от языка, список доступных языков можно узнать из системного представления sys.fulltext_languages (зависит от версии SQL Server, в 2005м нужно было некоторые языки (в том числе и русский) регистрировать &lt;a href="http://technet.microsoft.com/ru-ru/library/ms345188(SQL.90).aspx" target="_blank"&gt;вручную&lt;/a&gt;). Если выберем &amp;quot;неправильный&amp;quot; язык (например в поле, индексируемом согласно правилам английского языка, будем хранить русский текст), то словоформы работать не будут - все слова будут искаться &amp;quot;как есть&amp;quot; (буквально). Проблемы также возникнут с синонимами и &amp;quot;похожестью&amp;quot; слов. &lt;/li&gt;    &lt;li&gt;фильтрация содержимого: фактически дает возможность проиндексировать документы, хранящиеся в БД (в поле типа image/varbinary(max)). Хорошая новость заключается в том, что поддерживаются все форматы, которые &amp;quot;понимает&amp;quot; Windows, на которой выполняется SQL Server - нужно лишь задать специальное поле, в котором для данной строки будет храниться формат потока (фактически расширение файла). &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Самая большая проблема, конечно же, заключается в правильной обработке мультилокальных данных (и в нашем глобальном плоском мире с каждым днем все меньше и меньше приложений, который могут обойтись поддержкой одного языка). Альтернатив целых две:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Хранить данные для всех языков в одних и тех же полях вперемешку - проще всего, но теряется значительная часть функциональности&lt;/li&gt;    &lt;li&gt;Для каждого языка создавать отдельное поле/поля (например Title_EN, Title_RU и т.д.) - функциональность остается при нас, но придется довольно много &amp;quot;плясать&amp;quot; вокруг этих данных (в том числе и в приложении - чтобы правильно сохранить/загрузить)&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Что бы мы ни выбрали в конечном итоге, за языковыми настройками индексации каждого поля (как и за collation ;-) ) надо следить.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Поддержка/сопровождение&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Здесь хорошая новость заключается в том, что полнотекстовые каталоги являются частью базы данные и, как следствие, входят в состав резервной копии/восстанавливаются. Более сложным является вопрос поддержания полнотекстовых индексов в актуальном состоянии. Здесь есть два основных варианта:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;автоматическое наполнение/обновление (CHANGE_TRACKING AUTO): сервер сам будет следить за актуальностью индексов - хорошее решение со многих точек зрения, но могут возникнуть некоторые неожиданные побочные эффекты, связанные с тем, что к нашей базе данных будет обращаться еще один (системный) процесс, накладывающий некоторое количество блокировок &lt;/li&gt;    &lt;li&gt;наполнение/синхронизация индексов по запросу (CHANGE_TRACKING MANUAL): больше ответственности, но и больше уверенности в том что и когда мы делаем - может потребоваться в основном при большой загрузке, когда мы хотим максимально разгрузить сервер БД в течении рабочего времени и готовы смириться с некоторой степенью неактуальности информации в индексах &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Довольно сложно дела обстоят и с мониторингом/оптимизацией производительности. Microsoft не разглашает внутреннюю структуру хранения полнотекстовых индексов поэтому на них не распространяется опыт оптимизации &amp;quot;обычных&amp;quot; индексов. Единственное, что можно сказать наверняка: чем больше индексов (в том числе и полнотекстовых) &amp;quot;навернуто&amp;quot; на таблицу, тем больше времени будут занимать операции добавления/обновления/удаления данных.&lt;/p&gt;  &lt;p&gt;Ниже приведено сравнение планов добавления записи в две одинаковые таблицы, отличающиеся только тем, что для одной из них создан полнотекстовый индекс по одному из полей с автоматическим обновлением. Та часть плана первого запроса, которая не влезла на экран, полностью совпадает с планом второго запроса.&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh6.ggpht.com/_0v8-WQDReFE/SezOaFWJt4I/AAAAAAAADpw/HabKZjG4Cq8/s1600-h/image8.png"&gt;&lt;img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="268" alt="image" src="http://lh6.ggpht.com/_0v8-WQDReFE/SezOfTrctDI/AAAAAAAADp0/Ty2NmNiEYxA/image_thumb4.png?imgmax=800" width="658" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Выводы делаем сами (впрочем, я думаю и так понятно, что бесплатного в этом мире ничего не бывает).&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Альтернативы&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Если не рассматривать в качестве альтернативы переход на другую СУБД (в PostgreSQL, Oracle и MySql есть аналогичные технологии), то остается применение неких внешних по отношению к нашему приложению сервисов, производящих индексацию содержимого. Тут можно упомянуть:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&lt;a href="http://msdn.microsoft.com/en-us/library/aa965362(VS.85).aspx" target="_blank"&gt;Windows Search&lt;/a&gt; - встроен в Windows (следовательно &amp;quot;бесплатен&amp;quot;), позволяет индексировать файлы, хранящиеся на компьютере &lt;/li&gt;    &lt;li&gt;&lt;a href="http://desktop.google.com/" target="_blank"&gt;Google Desktop&lt;/a&gt; - аналогичный продукт от Google &lt;/li&gt;    &lt;li&gt;Apache &lt;a href="http://lucene.apache.org/" target="_blank"&gt;Lucene&lt;/a&gt; / &lt;a href="http://lucene.apache.org/solr/" target="_blank"&gt;Solr&lt;/a&gt; - индексатор / поисковый сервер от Apache Foundation &lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Первые два продукта скорее ориентированы на индексацию файлов и подойдут скорее в том случае, если перед нами поставлена задача индексировать какие-то документы (довольно широкая тема), в то время как последний - именно внешний индекс, который может &amp;quot;втянуть&amp;quot; почти все, что угодно, физически представляет из себя веб-службу, поддерживает уйму разного функционала, распределенные индексы, очень гибко настраивается и много чего еще. Об этом чудо-средстве я планирую написать отдельный пост (а может быть и не один).&lt;/p&gt;  &lt;p&gt;&amp;#160;&lt;/p&gt;  &lt;p&gt;HTH&lt;/p&gt;  &lt;p&gt;AlexS &lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-1448127549449272454?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/1448127549449272454/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/04/ms-full-text-search.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1448127549449272454'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1448127549449272454'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/04/ms-full-text-search.html' title='Полнотекстовый поиск: MS Full-Text Search'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh6.ggpht.com/_0v8-WQDReFE/SezOfTrctDI/AAAAAAAADp0/Ty2NmNiEYxA/s72-c/image_thumb4.png?imgmax=800' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-744978735786635186</id><published>2009-03-12T20:34:00.001+02:00</published><updated>2009-03-12T20:34:11.943+02:00</updated><title type='text'>Полнотекстовый поиск: введение</title><content type='html'>&lt;p&gt;Данным постом я начинаю серию статей о полнотекстовом поиске. Для начала определимся с тем, что это такое и зачем это нужно. &lt;/p&gt;  &lt;p&gt;Итак, можно выделить несколько основных способов поиска информации.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Поиск подстроки в строке&lt;/em&gt;&lt;/strong&gt; - самый простой вариант, грубо говоря сканируем все строки/документы и проверяем в каких из них содержится заданная последовательность символов.&lt;/p&gt;  &lt;p&gt;&lt;em&gt;&lt;strong&gt;Поиск с помощью регулярных выражений&lt;/strong&gt;&lt;/em&gt; - более сложный (и функциональный) вариант поиска подстроки в строке. Регулярное выражение задает правило, по которому формируется набор строк, которые в свою очередь ищутся в строках/документах. В общем случае требует гораздо больше ресурсов, чем простой поиск подстроки в строке.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;&amp;quot;&lt;/em&gt;&lt;/strong&gt;&lt;a href="http://en.wikipedia.org/wiki/Full_text_search" target="_blank"&gt;&lt;strong&gt;&lt;em&gt;Полнотекстовый поиск&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;&lt;em&gt;&amp;quot;&lt;/em&gt;&lt;/strong&gt; - технология, призванная увеличить объемы обрабатываемой информации, скорость поиска и функциональность. Так, например, используя первые два способа, достаточно сложно найти все строки/документы, содеражащие некоторое слово (с учетом весех возможных словофор) - требуется либо несколько запросов, либо достаточно сложное регулярное выражение (да и то не всегда это помогает). В ситуации когда мы хотим найти несколько слов (которые могут идти не подряд), ситуация еще больше усложняется.&lt;/p&gt;  &lt;p&gt;Итак, основная особенность полнотекстового поиска заключается в том, что мы ищем не &amp;quot;строгую последовательность символов&amp;quot;, а слова (со всеми словоформами). Большинство продуктов, реализующих эту технологию, также позволяют искать &amp;quot;слова, похожие на заданное&amp;quot;, использовать булевые операторы и т.п. Кроме того, все коммерческие реализации ищут не по самим строкам/документам, а по индексу, построенному для них.&lt;/p&gt;  &lt;p&gt;Таким образом, каждая строка/документ подвеграется предварительному анализу (индексируется). В ходе этого анализа в общем случае нужно выполнить следующие действия:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Разбить текст на слова (более точно: лексемы) - за это отвечает лексический аналазитор (&lt;a href="http://en.wikipedia.org/wiki/Tokenizer#Tokenizer" target="_blank"&gt;tokenizer&lt;/a&gt;). &lt;/li&gt;    &lt;li&gt;Произвести фильтрацию полученных слов (пропустить некоторые из них (стоп-список), обработать одинаковые и т.п.) - за это отвечает (необязательный) фильтр лексем (token filter). &lt;/li&gt;    &lt;li&gt;Найти основу каждого слова (стем) - за это отвечает стеммер (&lt;a href="http://en.wikipedia.org/wiki/Stemming" target="_blank"&gt;stemmer&lt;/a&gt;). &lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Далее в индексе сохраняется информация о том, какие стемы были найдены в конкретной строке/документе и, возможно, еще какая-то информация (взаимное расположение стемов, их количество и т.п.).&lt;/p&gt;  &lt;p&gt;В ходе поиска запрос обрабатывается по-тому же алгоритму, что и исходные строки/документы (хотя возможны и отличия, например на стадии фильтрации). Далее, фактически, мы ищем в индексе те строки/документы, которые содержат стемы, полученные в рзультате обработки поискового запроса (с учетом булевых операторов). Кроме этого, поиск может быть сдела более интеллектуальным: поиск слов, похожих, на заданное, поиск документов, в которых заданные слова встречаются рядом, и т.п. - здесь все зависит от функциональности составленного нами индекса.&lt;/p&gt;  &lt;p&gt;Далее я планирую остановиться на двух реализциях данной технологии: MS SQL Server &lt;a href="http://technet.microsoft.com/en-us/library/cc723263.aspx" target="_blank"&gt;Full Text Search&lt;/a&gt; и Apache &lt;a href="http://lucene.apache.org/solr/" target="_blank"&gt;Solr&lt;/a&gt;.&lt;/p&gt;  &lt;p&gt;HTH&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-744978735786635186?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/744978735786635186/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/03/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/744978735786635186'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/744978735786635186'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/03/blog-post.html' title='Полнотекстовый поиск: введение'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-8100351305821133278</id><published>2009-03-02T23:17:00.001+02:00</published><updated>2009-03-02T23:17:44.221+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Для быстрой страницы шепчите на ушко богам SQL-я</title><content type='html'>&lt;p&gt;Давно собирался изложить, да вот все никак руки не доходили. А тут совсем меня тоска зеленая загрызла (по былым денькам) - понял, что сегодня или никогда.&lt;/p&gt;  &lt;p&gt;Итак, речь пойдет о списочных представлениях (aka list view). Я уже как-то ранее упоминал о них &lt;a href="http://dbbuilder.blogspot.com/2009/01/blog-post.html" target="_blank"&gt;здесь&lt;/a&gt;. Вкратце напомню особенности этого способа отображения данных:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;отображаются данные сразу из нескольких таблиц (от двух до десятка)&lt;/li&gt;    &lt;li&gt;предполагается сортировка по любому полю&lt;/li&gt;    &lt;li&gt;предполагается разбиение данных на страницы (для удобства просмотра)&lt;/li&gt;    &lt;li&gt;чаще всего пользователь имеет возможность фильтровать данных по различным (порой довольно сложным) условиям&lt;/li&gt;    &lt;li&gt;в большенстве случаев НЕ предполагается inline-редактирование данных (в силу пункта 1)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Об объектной модели и технологиях реализации я сейчас говорить не буду, а сконцентрируюсь на уровне доступа к данным. Не будем заморачиваться также на способ получения данных: хранимые процедуры (что врядли) или динамическая генерация SQL-кода (рекомендую, хотя тут тоже не все так просто). Пусть у нас уже есть костяк запроса и мы хотим чтобы он работал побыстрее.&lt;/p&gt;  &lt;p&gt;Итак, чтобы быть более конкретным, возьмем две таблицы (простейший случай):&lt;/p&gt;  &lt;p&gt;CREATE TABLE [dbo].[CityData](   &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [id] [int] IDENTITY(1,1) NOT NULL,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [CityName] [varchar](50) NULL,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [Province] [varchar](50) NULL,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [ZipCode] [varchar](10) NULL);&lt;/p&gt;  &lt;p&gt;CREATE TABLE [dbo].[PersonData](   &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [Id] [int] IDENTITY(1,1) NOT NULL,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [CityId] [int] NULL,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [FullName] [varchar](255) NULL,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [ZipCode] [varchar](10) NULL,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [Email] [varchar](255) NULL,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; [ContactPhone] [varchar](100) NULL);&lt;/p&gt;  &lt;p&gt;Отображать будем данные о людях (PersonData) вместе с информацией о городе:&lt;/p&gt;  &lt;p&gt;select *   &lt;br /&gt;from    &lt;br /&gt;(    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; select    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; p.Id, p.FullName, c.CityName,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ROW_NUMBER() OVER (ORDER BY p.FullName) as RowNumber    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; from     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; PersonData p    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; left join CityData c on c.id = p.CityId    &lt;br /&gt;) q    &lt;br /&gt;where q.RowNumber between @StartRow and @EndRow&lt;/p&gt;  &lt;p&gt;Все хорошо, НО: зачем присоединять данные о городах ко всем записям из таблицы PersonData в то время, как нам нужно получить всего лишь (@EndRow - @StartRow) записей? Правильно, севершенно незачем. Поэтому можем сделать так:&lt;/p&gt;  &lt;p&gt;select   &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; q.*,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; c.CityName    &lt;br /&gt;from    &lt;br /&gt;(    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; select    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; p.Id, p.FullName, p.CityId,    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; ROW_NUMBER() OVER (ORDER BY p.FullName) as RowNumber    &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160; from     &lt;br /&gt;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160;&amp;#160; PersonData p    &lt;br /&gt;) q    &lt;br /&gt;left join CityData c on c.id = q.CityId    &lt;br /&gt;where q.RowNumber between @StartRow and @EndRow&lt;/p&gt;  &lt;p&gt;Что изменилось? Пришлось явно выбирать p.CityId (чтобы потом можно было присоединить данные о городах) и ... в общем-то все. Каков результат? Вот что говорит нам план выполнения запроса:&lt;/p&gt;  &lt;p&gt;&lt;a href="http://lh3.ggpht.com/_0v8-WQDReFE/SaxM6bO1pNI/AAAAAAAADZM/n3BLoCQd-kk/s1600-h/image%5B5%5D.png"&gt;&lt;img style="border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px" height="315" alt="image" src="http://lh5.ggpht.com/_0v8-WQDReFE/SaxM8M4oEiI/AAAAAAAADZQ/2ahUX8PjkkA/image_thumb%5B3%5D.png?imgmax=800" width="659" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;таблица CityData в первом случае присоединяется ДО того, как происходит фильтрация данных для страницы, а во-втором ПОСЛЕ&lt;/li&gt;    &lt;li&gt;стоимость выполнения второго запроса в два раза меньше (это на нашем простом примере, где в PersonData 100 записей, а в CityData - 50)&lt;/li&gt;    &lt;li&gt;статистика ввода/вывода тоже вдохновляет: удалось почти вдвое сократить количество логических чтений на обработке таблицы CityData&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Примерно такая же картина наблюдается в MySQL и PostgreSQL (только план запроса не так наглядно выглядит).&lt;/p&gt;  &lt;p&gt;Несколько замечание касательно этой техники:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Чем больше таблиц нужно присоединять и чем больше у нас данных - тем значительнее выигрыш (хотя зависимость нелинейная)&lt;/li&gt;    &lt;li&gt;Те таблицы, которые связаны с главной таблицей через inner join, должны всегда быть во внутреннем запросе, а вот те, которые через left join можно смело выносить наружу&lt;/li&gt;    &lt;li&gt;Та таблица, по полю которой нужно сортировать результат, должна быть во внутреннем запросе&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;HTH&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-8100351305821133278?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/8100351305821133278/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/03/sql.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8100351305821133278'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8100351305821133278'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/03/sql.html' title='Для быстрой страницы шепчите на ушко богам SQL-я'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh5.ggpht.com/_0v8-WQDReFE/SaxM8M4oEiI/AAAAAAAADZQ/2ahUX8PjkkA/s72-c/image_thumb%5B3%5D.png?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-2503496038096557747</id><published>2009-02-24T00:19:00.001+02:00</published><updated>2009-02-24T00:24:54.866+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SMO'/><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><title type='text'>Использование и настройка псевдонимов (server alias)</title><content type='html'>&lt;p&gt;Сегодня почти весь день (с перерывами на работу) пришлось заниматься &amp;quot;разруливанием проблем&amp;quot; с приложением на стороне заказчика. Один из сервисов все никак не хотел подключаться к SQL Server-у. Проблема усугублялась тем, что у нашей команды нет доступа к целевому окружению (вообще никакого доступа). Есть доступ только к индусам, которые в свою очередь имеют доступ к этому самому окружению. Причем &amp;quot;интерфейс&amp;quot; к индусам - электронная почта и Skype. После многочисленных советов &amp;quot;разобраться в конфигурации&amp;quot; и столь же многочисленных ответов &amp;quot;все точно так же, как и на стэйджинге&amp;quot;, причина таки нашлась: на &amp;quot;проблемном&amp;quot; сервере не был сконфигурирован псевдоним (alias). &lt;/p&gt;  &lt;p&gt;Мне сразу по приходу в этот проект не понравилось использование этой &amp;quot;фичи&amp;quot; - и вот опасения материализовались (правда это первый раз когда все было так серьезно). Не могу не признать, что в определенной степени alias - это удобно. Он, например, дает свободу в выборе сервера: можно переключиться с &amp;quot;общественного&amp;quot; на локальный меняя не строку подключения, а настройки псевдонима - т.е. действуя снаружи приложения. Причем псевдоним действует в рамках комьютера, т.е. (потенциально) можно влиять сразу на целый пакет приложений и сервисов. Побочный эффект: пока не посмотришь в настройки - не узнаешь куда же приложение &amp;quot;ходит&amp;quot; за данными. В целом я бы, наверное, избегал использования этой возможности.&lt;/p&gt;  &lt;p&gt;Возник, однако, другой вопрос. В случае, когда на компьютере установлен SQL Server Client Tools (на машине разработчика есть с вероятностью 99%) все просто: запускаем SQL Server Configuration Manager, идем в раздел SQL Native Client Configuration, далее Aliases. А если этого пакета нет (на сервере - не редкость)? Как быть? Вариантов несколько:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;&amp;quot;для слабаков&amp;quot;: установить SQL Server Client Tools и получить SQL Server Configuration Manager&lt;/li&gt;    &lt;li&gt;&amp;quot;для простых админов&amp;quot;: в комплекте с клиентскими библиотеками SQL Server идет утилита cliconfg.exe (находится в папке Windows\systems32 так что можно вызывать из командной строки), в которой можно сконфигурировать не только псевдоним, но и много чего еще&lt;/li&gt;    &lt;li&gt;&amp;quot;для хардкорных админов&amp;quot;: воспользоваться Power Shell и SMO (используя объект Microsoft.SqlServer.Management.Smo.Wmi.ServerAlias) - правда для этого придется установить на компьютер PowerShell (в Server 2008 и новее есть всегда) и собственно SMO&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Рекомендуемый вариант - &amp;quot;для админов&amp;quot;. Или &amp;quot;для хардкорных админов&amp;quot; (возможно с вариациями) - если нужно чтобы настройка происходила автоматически и/или была повторяемой. &lt;/p&gt;  &lt;p&gt;За деталями отсылаю к MSDN и документации по PowerShell. А еще рекомендую блог &lt;a href="http://blogs.msdn.com/sql_protocols/" target="_blank"&gt;SQL Protocols&lt;/a&gt; - такой подробной информации о протоколах SQL Server и решении проблем с соединением вы больше нигде не найдете.&lt;/p&gt;  &lt;p&gt;HTH&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-2503496038096557747?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/2503496038096557747/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/02/server-alias.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/2503496038096557747'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/2503496038096557747'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/02/server-alias.html' title='Использование и настройка псевдонимов (server alias)'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-510887036333433218</id><published>2009-02-09T21:41:00.001+02:00</published><updated>2009-02-09T21:41:07.080+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ADO.NET'/><category scheme='http://www.blogger.com/atom/ns#' term='MS SQL Server'/><category scheme='http://www.blogger.com/atom/ns#' term='SQL'/><title type='text'>Об опциях соединения вообще и об IDENTITY_INSERT в частности</title><content type='html'>&lt;p&gt;Сегодня пришлось &amp;quot;играться&amp;quot; с IDENTITY_INSERT. С удивлением &lt;strike&gt;обнаружил&lt;/strike&gt; прочувствовал что это таки &amp;quot;установка уровня соединения&amp;quot;. Не скажу, что это прямо так удивительно, в MSDN об этом явно сказано:&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;At any time, only one table in a session can have the IDENTITY_INSERT property set to ON. If a table already has this property set to ON, and a SET IDENTITY_INSERT ON statement is issued for another table, SQL Server 2000/2005/2008 returns an error message that states SET IDENTITY_INSERT is already ON and reports the table it is set ON for.&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;но как-то до сих пор я не особенно обращал внимание на это примечание.&lt;/p&gt;  &lt;p&gt;Ну да ладно. Сама ситуация в очередной раз напомнила мне о том, что опции соединения - это вам не просто так и что за ними нужно следить. Главная проблема (с точки зрения программиста) с ними заключается даже не в том, как они инициализируются (это отдельная непростая тема), а в том, что они не сбрасываются при закритии соединения (точнее: при возвращении соединения в пул).&lt;/p&gt;  &lt;p&gt;Для того, чтобы постигнуть &amp;quot;всю глубину наших глубин&amp;quot;, рассмотрим такой сценарий. Мы выполняем некоторый SQL код и хотим повысить уровень его изоляции:&lt;/p&gt;  &lt;p&gt;using(SqlConnection = new SqlConnection())&lt;/p&gt;  &lt;p&gt;{&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;con.Open();&lt;/p&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;SqlCOmmand com = new SqlCommand(&amp;quot;SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- очень важный SQL код&amp;quot;, con);&lt;/p&gt; &lt;/blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;com.ExecuteNonQuery();&lt;/p&gt; &lt;/blockquote&gt;  &lt;p&gt;}&lt;/p&gt;  &lt;p&gt;отлично. Все вроде бы довольны. Кроме того несчастного, которому в очередной раз попадется из пула именно это соединение с уровнем изоляции ... SERIALIZABLE. Для выполнения монстроидального запроса, объединяющего три-четыре-пять не самых маленьких таблиц. Приплыли ... ловить ошибки, возникающие в результате такого неосторожного обращения с опциями соединения, можно ооооочень долго.&lt;/p&gt;  &lt;p&gt;Граждане! Будьте бдительны применяя опции соединения (особенно уровень изоляции).&lt;/p&gt;  &lt;p&gt;HTH&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-510887036333433218?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/510887036333433218/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/02/identityinsert.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/510887036333433218'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/510887036333433218'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/02/identityinsert.html' title='Об опциях соединения вообще и об IDENTITY_INSERT в частности'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-8169259670959027251</id><published>2009-01-28T00:06:00.000+02:00</published><updated>2009-01-29T00:07:06.980+02:00</updated><title type='text'>Мой первый тренинг в качестве MCT</title><content type='html'>&lt;p&gt;Легкий оффтоп.&lt;/p&gt;  &lt;p&gt;Сегодня закончился мой первый тренинг в качестве сертифицированного тренера Microsoft.&lt;/p&gt;  &lt;p align="center"&gt;&lt;a href="http://lh5.ggpht.com/_0v8-WQDReFE/SYDW-7XdztI/AAAAAAAADTk/0KyaWHIhjtU/s1600-h/IMG_4536%5B2%5D.jpg"&gt;&lt;img style="border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px" height="164" alt="IMG_4536" src="http://lh4.ggpht.com/_0v8-WQDReFE/SYDW_8c4TnI/AAAAAAAADTo/3OM8j4fLq7Q/IMG_4536_thumb.jpg?imgmax=800" width="244" border="0" /&gt;&lt;/a&gt; &lt;a href="http://lh4.ggpht.com/_0v8-WQDReFE/SYDXBQeSy_I/AAAAAAAADTs/-2UY5Y65xd4/s1600-h/IMG_4537%5B2%5D.jpg"&gt;&lt;img style="border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px" height="164" alt="IMG_4537" src="http://lh3.ggpht.com/_0v8-WQDReFE/SYDXCcDhmVI/AAAAAAAADTw/8Vam1nvqXos/IMG_4537_thumb.jpg?imgmax=800" width="244" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Группа была не слишком многочисленная, но бодрая - держали меня в тонусе и постоянно задавали вопросы &amp;quot;вширь и вглубь&amp;quot;. Отчасти именно из-за моего неумения вовремя остановить свои пояснения приходилось жертвовать практикой, отдавая ее на откуп самостоятельной (считай домашней) работе.&lt;/p&gt;  &lt;p&gt;На собственном опыте убедился, что подобный тренинг и преподавание в ВУЗе - это далеко не одно и тоже. Во-первых трениг проходит интенсивнее, чем обычные занятия, во-вторых очень здорово помогает наличие учебных материалов. Ну и кроме того, методика подачи материала совсем другая, нет разделения на лекции/практику, тут &amp;quot;все в одном&amp;quot;: рассказал - показал. В общем - новый опыт.&lt;/p&gt;  &lt;p&gt;Надеюсь первый блин все-таки не вышел комом.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-8169259670959027251?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/8169259670959027251/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/01/mct.html#comment-form' title='Комментарии: 1'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8169259670959027251'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8169259670959027251'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/01/mct.html' title='Мой первый тренинг в качестве MCT'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_0v8-WQDReFE/SYDW_8c4TnI/AAAAAAAADTo/3OM8j4fLq7Q/s72-c/IMG_4536_thumb.jpg?imgmax=800' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-8913660113143053515</id><published>2009-01-05T23:09:00.001+02:00</published><updated>2009-01-05T23:09:21.537+02:00</updated><title type='text'>Организация доступа к данным: как сделать шоколадно-конфетное приложение</title><content type='html'>&lt;p&gt;Дано: база данных, разработчик. Разработчик разрабатывает приложение. Приложение работает с данными, хранящимися в ... базе данных - отображает, редактирует, сохраняет.&lt;/p&gt;  &lt;p&gt;Цель: разработчик хочет сделать из приложения &amp;quot;конфетку&amp;quot; - чтобы и хорошо, и красиво, и производительно, и чтобы много пользователей, и умеренные требования к &amp;quot;железу&amp;quot;.&lt;/p&gt;  &lt;p&gt;Сразу ограничу область &amp;quot;приложения&amp;quot; - не слишком большое (не больше 2х - 3х слоев, ибо бизнес-логика присутствует), но и не слишком маленькое (т.е. о масштабировании думать уже нужно, и вариант &amp;quot;меня спасут помощники в Visual Studio&amp;quot; не пройдет). Также договоримся, что большая часть тех данных, с которыми работает приложение хранится именно в реляционной СУБД - т.е. для доступа к ним используется SQL (в той или иной форме).&lt;/p&gt;  &lt;p&gt;Несколько лет назад (5-6) в подобных случаях советовалось не хранить SQL в коде приложения, а выносить его в хранимые процедуры. Отлично. Результатом применения такого подхода является умопомрачительное количество этих самых процедур - по одной накаждую из &lt;a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete" target="_blank"&gt;CRUD&lt;/a&gt; -операций плюс еще что-то на бизнес-логику и логику отображения данных (как раз сейчас работаю с одним legacy приложением, в котором на 130 таблиц более 550 хранмых процедур). Такое положение дел не очень хорошо по целому ряду причин:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;сложно поддерживать&lt;/li&gt;    &lt;li&gt;сложно расширять&lt;/li&gt;    &lt;li&gt;сложно обновлять&lt;/li&gt;    &lt;li&gt;по производительности тривиальные хранимые процедуры, реализующие CRUD-операции, хуже динамически сгенерированного SQL-кода (особенно это касается операций обновления данных)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Новое время дает нам новые инструменты. На замену хранимым процедурам пришли &lt;a href="http://ru.wikipedia.org/wiki/ORM" target="_blank"&gt;ORM&lt;/a&gt;-библиотеки. Вроде бы все стало хорошо, мило и приятно - теперь уже можно вообще не писать SQL код. Но не все так просто. Да, с CRUD-операциями ORM справляется хорошо, но вот бизнес-логика зачастую вполне ощутимо &amp;quot;подтормаживает&amp;quot;, т.к. требует извлечения довольно большого количества сущностей из БД. Если мы хотим создать приложение, которое будет &amp;quot;быдро пошевеливаться&amp;quot;, то полностью отказываться от хранимых процедур нам нельзя - проверку сложной бизнес-логики, работающей на множестве объектов, из которых состоит/которыми оперирует наша система, лучше держать поближе к хранилищу этих объектов, т.е. в БД.&lt;/p&gt;  &lt;p&gt;Но и тут наша история еще не заканчивается. Если взглянуть на типичное современное (веб-)приложение, то половина (если не больше) его функциональности - это отображение различных списков. Причем чаще всего эти списки имеют похожую функциональность:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;необходимо обеспечить сортировку по широкому спектру атрибутов (что делает невозможным применение хранимых процедур для получения данных)&lt;/li&gt;    &lt;li&gt;отображаемые данных &amp;quot;собираются&amp;quot; из нескольких сущностей (что ставит под сомнение эффективность применения ORM)&lt;/li&gt;    &lt;li&gt;необходимо обеспечить разбивку на страницы (что вкупе с первыми двумя пунктами &amp;quot;напрягает&amp;quot; и хранимые процедуры и ORM-средства)&lt;/li&gt;    &lt;li&gt;наличие функциональности по фильтрации данных (так что либо мы изощренно издеваемся над ORM, генерирую хитрые условия, либо динамически генерируем SQL-код в хранимых процедурах)&lt;/li&gt;    &lt;li&gt;в большинстве случаев данные, отображаемые в списке, не нужно редактировать (для этого есть отдельная страница)&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;По моему опыту, именно такие запросы сильнее всего загружают сервер баз данных. Можно, конечно, решить все перечисленные выше проблемы &amp;quot;методом лома&amp;quot; - грубой силой заставив выбранную технологию (хранимые процедуры или ORM) делать то, что требуется. Но тогда наверняка пострадает гибкость и производительность решения. А мы хотим &amp;quot;конфетку&amp;quot;. В итоге мы приходим к необходимости еще одного нишевого решения - динамической генерации SQL для отображения списков (list view). Положительный эффект на уровне БД при этом может достигать 50 - 60% (по сравнению с вариантом &amp;quot;в лоб&amp;quot;).&lt;/p&gt;  &lt;p&gt;Итак, с моей точки зрения, сбаллансированный подход к организации доступа к данных (СУБД) заключается в следующем:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;слой бизнес-логики (aka domain model) с применением ORM&lt;/li&gt;    &lt;li&gt;бизнес-логика проверяется на сервере БД и (по крайнем мере часть ее) материализуется в виде хранимых процедур&lt;/li&gt;    &lt;li&gt;для отображения списков используется динамическая генерация SQL-кода, которая позволяет получить эффективный (SQL-)код и уменьшить нагрузку на сервер БД&lt;/li&gt; &lt;/ul&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-8913660113143053515?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/8913660113143053515/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2009/01/blog-post.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8913660113143053515'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8913660113143053515'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2009/01/blog-post.html' title='Организация доступа к данным: как сделать шоколадно-конфетное приложение'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-7595054656829339550</id><published>2008-12-22T00:41:00.001+02:00</published><updated>2008-12-22T00:41:11.425+02:00</updated><title type='text'>Тестирование производительности базы данных</title><content type='html'>&lt;p&gt;Решил изложить некоторые мысли на эту тему: не претендую ни на полноту обзора, ни на абсолютность своего мнения.&lt;/p&gt;  &lt;p&gt;Итак, когда речь заходит о &lt;a href="http://en.wikipedia.org/wiki/Software_performance_testing" target="_blank"&gt;тестировании производительности&lt;/a&gt;, обычно выделяют:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;нагрузочный тест (load test) - насколько хорошо мы можем выдержать плановую загрузку (сохранив при этому заданные показатели производительности)&lt;/li&gt;    &lt;li&gt;стресс-тест (stress test) - под какой нагрузкой мы сломаемся и что при этом произойдет&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Тестирование производительности обычно проводят для каждой версии приложения и наблюдют не только и не столько за абсолютными цыфрами (хотя важны и они), сколько за динамикой - &amp;quot;мы внесли изменения в архитектуру и нам удалось улучшить производительность на X%&amp;quot;.&lt;/p&gt;  &lt;p&gt;Теперь разберемся что есть что применительно к базам данных. Во-первых, при попытке произвести тестирование производительности базы данных, мы неизбежно сталкиваемся с набором довольно специфических трудностей:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;настройки: база данных не является самодостаточной сущностью с точки зрения приложения - она работает под управлением сервера БД, от характеристик и настроек которого сильно зависит то, с какой скоростью наше приложение сможет считывать/записывать данные&lt;/li&gt;    &lt;li&gt;непрямое взаимодействие: пользователь приложения в подавляющем большинстве случаев не взаимодействует с базой данных напрямую, поэтому при неизменном с точки зрения пользователя функционале, разные версии приложения могут генерировать существенно разную нагрузку&lt;/li&gt;    &lt;li&gt;профиль нагрузки: производительность сильно зависит от того, как именно используется приложение (есть более &amp;quot;тяжелые&amp;quot; и более &amp;quot;легкие&amp;quot; операции), впрочем, эта проблема характерна для приложения вцелом&lt;/li&gt;    &lt;li&gt;масштабируемость нагрузки: производительность существенным (и нелинейным) образом зависит от объемов данных в базе и количества подключений (одновременно работающих пользователей)&lt;/li&gt;    &lt;li&gt;единицы измерения: сложно определить в каких &amp;quot;попугаях&amp;quot; мерять производительность базы данных - ведь не существует &amp;quot;единых универсальных запросов к базе данных&amp;quot;&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Попробуем разобраться с этими вопросами.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Настройки&lt;/em&gt;&lt;/strong&gt;. Тут особенно сказать нечего: да, от настроек (равно как и от оборудования) очень многое зависит. Нужно постараться воспроизвести настройки целевого окружения (production environment) и стараться не менять их от теста к тесту. С оборудованием сложнее. Идеальный вариант - тестировать на точной копии целевых серверов, но практически мало кто может себе такое позволить. Однако запуск тестов на одном и том же оборудовании с одним и тем же набором настроек позволит получить общую картини движения: ничего не поменялось, улучшилос, ухудшилось и т.п.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Непрямое взаимодействие&lt;/em&gt;&lt;/strong&gt;. По большому счету у нас здесь есть два пути:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;Нагружать базу данных через приложение. При этом мы фактически тестируем еще и приложение. Плюс здесь в том, что мы будет эмулировать работу пользователя (по-идее) - т.е. все будет так же, как и в реальных условиях. Минус в том, что сложнее измерять (т.к. все измерения будут относиться к системе в целом), требуется больше ресурсов (как программных, так и аппаратных) и сложнее масштабировать (увеличиваем число взаимодействующих подсистем - увеличиваем число мест, которые могут стать бутылочным горлышком).&lt;/li&gt;    &lt;li&gt;Нагружать базу данных напрямую. Здесь мы может свести к минимуму влияние других подсистем приложения, но можем столкнуться с необходиомостью реализации чуть ли не самого приложения с нуля (для эмуляции активности пользователя). Альтернативой является запись активности приложения с последующим его воспроизведением в несколько потоков.&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;Первый из этих способов более &amp;quot;правильный&amp;quot; и позволяет генерировать нагрузку, в наибольшей степени приближенную к реальности. Второй - проще реализовать, (в общем случае) проще масштабировать, да и к ресурсам он менее требователен.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Профиль нагрузки&lt;/em&gt;&lt;/strong&gt;. Имеет смысл завести несколько &amp;quot;пакетов&amp;quot;, в которые войдут разные наборы сценариев использования приложения. Можно составить некоторый обобщенный сценарий поведения пользователя (вроде: вошел в систему, почитал новости, зашел в раздел такой-то, потом туда-то, сдела то-то, вышел) и тестировать именно его. Несколько подобных профилей, составленных с учетом статистики использования реальной системы, позволят сгенерировать реалистичную нагрузку, в максимальной степени похожую на ту, которую генерируют реальные пользователи.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Масштабируемость нагрузки&lt;/em&gt;&lt;/strong&gt;. Здесь все зависит от целей, которые мы преследуем. Как уже было сказано, масштабировать нужно объем базы данных и количество подключений (одновременно работающих пользователей). Очевидное общее правило: чем больше данных, тем меньше пользователей сможет комфортно работать. Существуют утилиты, которые могу нагенерировать случайные данные для БД любой структуры. Но не всегда их можно применить: денормализованность схемы, неполное описание ограничений на уровне схемы, &amp;quot;хитрая&amp;quot; логика обработки данных, хранящаяся в хранимых процедурах или коде приложения - все это значительно затрудняет генерацию. Хорошим подспорьем будет какая-нибудь реальная БД с пользовательскими данными.&lt;/p&gt;  &lt;p&gt;&lt;strong&gt;&lt;em&gt;Единицы измерения&lt;/em&gt;&lt;/strong&gt;. Вот здесь, пожалуй, одна из самых больших проблем. Мы можем измерять нагрузку на сервер, на котором работает наша СУБД с тем, чтобы понять, справляется он или нет с заданной нагрузкой и что меняется от версии к версии. В этой связи полезно измерять:&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;среднюю загрузку процессора (CPU, %) - показатель в 60-70% считается нормальным (допустимым), если больше - сервер не справляется&lt;/li&gt;    &lt;li&gt;очередь ввода/вывода (Average disk queue length) - даже значение в несколько единиц свидетельствует о проблемах с подсистемой ввода/вывода (должно быть 0)&lt;/li&gt;    &lt;li&gt;количество запросов на блокировки в нашей базе (locks/sec) - чем меньше, чем лучше, важен прогресс от теста к тесту и изменение интенсивности этой величины в течении теста; всплески позволят идентифицировать наиболее тяжелые действия, которые нуждаются в оптимизации в первую очередь&lt;/li&gt;    &lt;li&gt;среднее время ожидания блокировки (msec) - показывает сколько нам приходится ждать окончания работы других потоков (чем меньше - тем лучше)&lt;/li&gt; &lt;/ol&gt;  &lt;p&gt;В ходе тестирования всегда стоит запустить какой-нибудь инструмент мониторинга запросов к БД с тем, чтобы потом можно было наложить полученный трэйс на данные о производительности и понять, как они соотносятся, найти наболее &amp;quot;тяжелые&amp;quot; операции (запросы) и уделить внимание их оптимизации.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-7595054656829339550?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/7595054656829339550/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2008/12/blog-post.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/7595054656829339550'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/7595054656829339550'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2008/12/blog-post.html' title='Тестирование производительности базы данных'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-3049869984541608113</id><published>2008-12-14T15:24:00.003+02:00</published><updated>2008-12-15T13:31:38.931+02:00</updated><title type='text'>Self service analytics: проект Gemini</title><content type='html'>&lt;p&gt;Ну что, дорогие читатели (или товарищ майор, фильтрующий Интернет), сегодня я расскажу вам о том, что Microsoft имеет предложить нам в не таком уж далеком будущем. Под "нами" я имею в виду не только разработческую братию, но и (что важнее) "обычных пользователей" (ну не совсем обычных, а скорее power users). Так что устраивайтесь поудобнее, доставайте поп-корн.&lt;/p&gt;  &lt;p&gt;Итак, история началась довольно давно: с выходом SQL Server 2005 и Report Builder. Хотя, пожалуй, даже раньше: когда в Microsoft задумали увеличить привлекательность SQL Server-а путем увеличения функциональности последнего - добавили Data Transformation Services (ныне Integration Services), потом Analysis Services и Reporting Services. С количеством установленных копий все складывалось удачно (даже очень). Но со временем пришло понимание того, что Business Intelligence - штука гораздо более "пользовательская", чем те же базы данных (разработка схемы данных, организация хранения, оптимизация производительности запросов - задачи не из простых и врядли интересны конечным пользователям). Кроме того, схема: "нам нужен анализ" -&amp;gt; "обращаемся к IT-парням" -&amp;gt; "парни что-то делают (разрабатывают, куда-то что-то размещают, настраивают...)" -&amp;gt; "(не прошло и полгода как) мы получили что хотели (ну или почти что хотели)". И в тех случаях, когда получается не совсем то, что хотелось, приходится повторять все это сначала. По-идее создавать модель анализа не так уж и сложно (с точки зрения программиста) - инструментарий тот же (Visual Studio), только можно обойтись без программирования. Но все-равно это совсем не тоже самое, что работать с (привычным почти всем офисным работникам) Excel-ем.&lt;/p&gt;  &lt;p&gt;Результат чаще всего такой: решения, разрабатываемые с благословения начальства, "крутятся" сами по себе, а большая часть пользователей работает сама по себе, решая свои задачи доступными им способами (рабочие книги Excel с массой сложных вычислений, макросов и ссылками на другие рабочие книги). В иторе имеем массу "приложений", нигде не учитываемую, неподконтрольную никому и с сомнительной достоверностью данных, но при этом весьма активно используемую - головная боль как для пользователей, так и для IT-служб.&lt;/p&gt;  &lt;p&gt;Microsoft берется за решение этих проблем под лозунгом "People Ready BI". Первые шаги в этом направлении были сделаланы с выпуском Report Designer - продуктом для создания отчетов, который ориентирован на конечных пользователей (есть уже &lt;a href="http://www.microsoft.com/downloads/details.aspx?familyid=9f783224-9871-4eea-b1d5-f3140a253db6&amp;amp;displaylang=en" target="_blank"&gt;версия 2.0&lt;/a&gt; с интерфейсом в стиле Office 2007). Следующий шаг - проект с кодовым именем Gemini, целью которого является аналитическая обработка данных на стороне клиента. То, что хотелось бы получить:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;для пользователей: привычные инструменты, возможность не обращаться к "программистам", возможность совместного использования полученного решения (aka collaboration);&lt;/li&gt;    &lt;li&gt;для "программистов": возможность хоть как-то контролировать то, что производится пользователями (или хотябы знать об этом) и отслеживать источники данных.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;С технической точки зрения нам предлагается следующее:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;для пользователей: расширение к Excel, позволяющее проводить аналитическую обработку данных на клиентской машине (in-memory, column-based analytics), для совместной работы используется SharePoint;&lt;/li&gt;    &lt;li&gt;для "программистов" (они же IT-отдел): SharePoint, в который в каком-то виде встраивается Analysis Services со средствами мониторинга загржаемых пользователями моделей, обработка этих моделей по расписанию и т.п.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Впервые эта технология была продемонстирована на ежегодной Microsoft Business Intelligence Conference в октябре этого года. Демонстрация и правда выглядит впечатляюще: довольно просто и в тоже время вполне фукнционально для большинства несложных моделей анализа. Там же огласили планы: выпуск в первом полугодии 2010 г, CTP - в течении 12 месяцев. Хотя позже было сказано (в кулуарах) о первом CTP в декабре этого года (правда не очень-то публичном), и о втором CTP ближе к лету 2009 г.&lt;/p&gt;  &lt;p&gt;Техническая аргументация в пользу "пользовательской аналитики" достаточно проста: бОльшая (если не подавляющая) часть аналитических моделей укладывается в размер 1 Гб, что вполне под силу нынешнему парку клиентских компьютеров (ноутбук, на котором я пишу эту заметку, имеет 2 Гб оперативной памяти - и ему уже больше года), не говоря уже о том, что будет через полтора года (в момент официального выхода) - так что волноваться, по-идее не о чем. Помимо этого была обещана очень эффективная компрессия данных, что по-идее должно положительно сказаться как на требованиях к памяти, так и на объеме полученных файлов. Таким образом на долю "обычного" Analysis Services остается многопользовательский доступ, сложные вычисления и сложные (и большие) модели с высокой нагрузкой. Что пока неясно:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;схема распространения: будет ли это "волшебное расширение" входить в стандартную поставку какой-то очередной версии Office (было бы хорошо, если бы входило)&lt;/li&gt;    &lt;li&gt;будет ли возможность конвертации "пользовательских моделей" в базу данных Analysis Services (почти уверен что будет, в той или иной форме, хотя об этом и не говорят)&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;В скобках нужно отметить, что Microsoft - не первая компания, открывшая для себя рынок in-memory аналитики. Стоит упомянуть хотябы &lt;a href="http://www.qlikview.com/" target="_blank"&gt;QlikView&lt;/a&gt; (для которой это основной бизнес), IBM, Oracle и SAP - каждая из последних за счет недавних поглощений явно выказали свой интерс к этому направлению.&lt;/p&gt;  &lt;p&gt;Так что будущее аналитики обещает быть интерсным (поговаривают даже о новом поклении OLAP).&lt;/p&gt;  &lt;p&gt;По метариалам:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;olapreport.com: &lt;a href="http://olapreport.com/Comment_Gemini.htm" target="_blank"&gt;Project Gemini — Microsoft’s Brilliant OLAP Trojan Horse&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.qlikview.com/"&gt;http://www.qlikview.com/&lt;/a&gt;&lt;/li&gt;    &lt;li&gt;&lt;a href="http://www.microsoft.com/winme/0810/34416/Ted_Kummert_MBR.asx" target="_blank"&gt;Доклад о развитии платформы обработки данных&lt;/a&gt; на Business Intelligence Conference&lt;/li&gt;    &lt;li&gt;&lt;a href="http://platforma2009.ru/"&gt;http://platforma2009.ru&lt;/a&gt; &lt;/li&gt; &lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-3049869984541608113?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/3049869984541608113/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2008/12/self-service-analytics-gemini.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/3049869984541608113'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/3049869984541608113'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2008/12/self-service-analytics-gemini.html' title='Self service analytics: проект Gemini'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-2885857270359728290</id><published>2008-12-07T19:32:00.001+02:00</published><updated>2008-12-07T19:32:54.743+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Events'/><title type='text'>"Платформа 2009"</title><content type='html'>&lt;p&gt;Сегодня вернулся из Москвы - был на &lt;a href="http://platforma2009.ru/" target="_blank"&gt;&amp;quot;Платформе 2009&amp;quot;&lt;/a&gt;. Был на этой конференции в первый раз. По направленности она довольно сильно отличается от DevConnections на которой мне довелось побывать в прошлом году - более &amp;quot;айтишная&amp;quot;, нежели программистская. Хотя при этом все-равно крайне познавательная.&lt;/p&gt;  &lt;p&gt;Из того, кто/что запомнилось:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Доклад &amp;quot;Интернет-сервисы с высокой нагрузкой&amp;quot; (представители СКБ Контур) вызвал чувство дежавю: почти один-в-один те проблемы, с которыми приходилось сталкиваться во время работы в Stella Systems.&lt;/li&gt;    &lt;li&gt;Григорий Погульский (Microsoft) с докладом об управлении, основанном на политиках (Policy-Based Management) в SQL Server 2008. Крайне любопытно было узнать как же это сделано.&lt;/li&gt;    &lt;li&gt;Лабораторные работы по SQL Server 2008 с виртуальным образом &amp;quot;производства&amp;quot; sqlskills.com (Kimberly L. Tripp и Paul S. Randall, которых я имел счастье слушать на DevConnections'07 в Лас-Вегасе) - пожалуй это (такой образ) именно то, чего мне не хватало (а самому делать довольно долго и руки как-то никак не доходили ...).&lt;/li&gt;    &lt;li&gt;Валерий Ким (Microsoft) с докладом о будущем Analysis Services 2010 (codename &amp;quot;Kilimanjaro&amp;quot;). Точнее о той части продукта, которая будет отвечать за обработку данных на стороне клиента (in-memory analytics, codename &amp;quot;Gemini&amp;quot;). Впервые об этой технологии было объявлено на Microsoft BI Conference в начале октября. Но одно дело - прочитать скупой анонс, и совсем другое - послушать человека, который непосредственно участвует в разработке. Постараюсь в ближайшее время написать об этом поподробнее.&lt;/li&gt;    &lt;li&gt;&amp;quot;Функциональное программирование и параллельные вычисления&amp;quot; в исполнении Ивана Бодягина и Андрея Корявченко - о .NET Parallel FX. Кажется эта штука должна здорово упростить распараллеливание выполнения кода (ибо толково автоматизировать этот процесс нельзя).&lt;/li&gt;    &lt;li&gt;Большое количество известных людей: Marc Russinovich, David Chappel, Eric Rudder, Dmitry Robsman.&lt;/li&gt;    &lt;li&gt;Ключевые слова &amp;quot;Платформы 2009&amp;quot;: Azure, cloud computing, Windows 7.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;В целом - интересно и полезно.&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-2885857270359728290?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/2885857270359728290/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2008/12/2009.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/2885857270359728290'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/2885857270359728290'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2008/12/2009.html' title='&amp;quot;Платформа 2009&amp;quot;'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-8101804799120229380</id><published>2008-12-01T00:29:00.004+02:00</published><updated>2008-12-07T17:37:35.728+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SMO'/><category scheme='http://www.blogger.com/atom/ns#' term='dbbuilder'/><title type='text'>Ускорение SMO v.10: дело не в запросах</title><content type='html'>&lt;p&gt;Мне все никак не дает покоя причина увеличение скорости работы SMO v.10 (см. &lt;a href="http://dbbuilder.blogspot.com/2008/11/sql-server-management-objects-smo-v9.html" target="_blank"&gt;здесь&lt;/a&gt;). Все думал: ну ничего себе, насколько, оказывается, можно эффективно (или неэффективно) работать с метаданными! Возникло сильное желание узнать: как нужно и как не нужно делать.&lt;/p&gt;  &lt;p&gt;Разбор полетов начал с профайлера (слева - версия 10, справа - 9):&lt;/p&gt;  &lt;p style="text-align: center;"&gt;&lt;a href="http://lh3.ggpht.com/_0v8-WQDReFE/STMTkHaXxqI/AAAAAAAADAI/FAhV3dpXo8U/s1600-h/SMOv09VSv10InProfiler%5B13%5D.jpg"&gt;&lt;img style="border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px" height="343" alt="SMOv09VSv10InProfiler" src="http://lh4.ggpht.com/_0v8-WQDReFE/STMTmWB34PI/AAAAAAAADAM/TgL2QEOL3mQ/SMOv09VSv10InProfiler_thumb%5B7%5D.jpg?imgmax=800" width="551" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Говоря по-простому: на уровне запросов к базе не поменялось ничего. По содержанию. А вот форма немного изменилась. Если в версии 9 все запросы генерировались динамически (т.е. для каждого объекта создавался свой запрос), то в версии 10 используются запросы с явной параметризацией, что по-идее существенно облегчает работу сервера - не надо на каждый запрос создавать новый план выполнения. Мониторинг сервера во время создания скриптов базы с помощью &lt;a href="http://code.google.com/p/dbbuilder" target="_blank"&gt;dbbuilder&lt;/a&gt; показывает, следующее:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;для SMO v.9 практически на каждый запрос создается новый план выполнения (даже для однотипных запросов), повторного использования планов не происходит;&lt;/li&gt;    &lt;li&gt;для SMO v.10 план типовых запросов в большинстве случаев используется повторно.&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;В целом ожидаемо. Но дальше начинается интересное. Если проанализировать статистику выполнения этих запросов (суммарное время выполнения, общая нагрузка на подсистемы ввода/вывода и загрузка процессора), то обнаруживается, что разница между версиями во-первых не так уж и велика, а во-вторых, по "чистому времени выполения запросов" она даже не в пользу новой версии (v9: 19.5 s, v.10: 26 s).&lt;/p&gt;  &lt;p style="text-align: center;"&gt;&lt;a href="http://lh4.ggpht.com/_0v8-WQDReFE/STMTpnk6swI/AAAAAAAADAQ/fDY4avP0bj4/s1600-h/QuerySummary%5B5%5D.jpg"&gt;&lt;img style="border-right: 0px; border-top: 0px; border-left: 0px; border-bottom: 0px" height="370" alt="QuerySummary" src="http://lh5.ggpht.com/_0v8-WQDReFE/STMTrLCtsHI/AAAAAAAADAU/BTazL2xMjHI/QuerySummary_thumb%5B3%5D.jpg?imgmax=800" width="556" border="0" /&gt;&lt;/a&gt; &lt;/p&gt;  &lt;p&gt;Так что версию о том, что причина ускорения кроется в эффективности/неэффективности самих запросов (или способа их выполнения) скорее всего нужно отбросить.&lt;/p&gt;  &lt;p&gt;Начал было "ковыряться" в коде SMO, но что-то пока без особого успеха (слишком уж его много).&lt;/p&gt;  &lt;p&gt;Чисто для себя решил выяснить разницу по производительности (измеренную на уровне приложения) между параметризированными запросами и динамически сгенерированным SQL. Для простых запросов эта разница составляет около 20%, что тоже неплохо.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-8101804799120229380?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/8101804799120229380/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2008/12/smo-v10.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8101804799120229380'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/8101804799120229380'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2008/12/smo-v10.html' title='Ускорение SMO v.10: дело не в запросах'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://lh4.ggpht.com/_0v8-WQDReFE/STMTmWB34PI/AAAAAAAADAM/TgL2QEOL3mQ/s72-c/SMOv09VSv10InProfiler_thumb%5B7%5D.jpg?imgmax=800' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-1658691089521780342</id><published>2008-11-21T14:13:00.002+02:00</published><updated>2008-12-07T19:33:42.443+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Events'/><title type='text'>4я встреча IT Talk в Харькове</title><content type='html'>&lt;p&gt;Был вчера на IT Talk. Мероприятие привлекает довольно много людей и развивается. Теперь уже докладчики - не приглашенные "варяги", а те посетители, кто изъявил желание о чем-то рассказать. Но все-равно у меня лично целый ряд вопросов/замечаний:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;Так и не понял для себя: кто является целевой аудиторией (чисто визуально половина аудитории - студенты и интересующиеся, а половина - "представители индустрии"). При этом доклады по большому счету не ориентированы ни на одну из этих групп: для одних, пожалуй, чересчур сложно, а для других в основном беспредметно.&lt;/li&gt;    &lt;li&gt;Второй (или даже третий - точно не припомню) раз хожу туда и так и не пойму цели мероприятия (может, правда, это я лично ожидаю от него слишком многого). Мне бы виделось, что если мероприятие массовое, то и цель у него должна быть понятна (и близка) этим самым массам. А по факту получается полигон, на котором очень ограниченный круг товарищей "демонстрирует шары". Причем конструктивизма в большинстве высказываний ни на грош - они имеют целью либо показать какой говорящий "классный, всезнающий и профессиональный" или что докладчик "ничего не смыслит в колбасных обрезках".&lt;/li&gt;    &lt;li&gt;Организация: если мы хотим дискуссию (а судя по названию мероприятия мы таки ее хотим), то нужно либо организовывать круглый стол (с ограниченным числом говорящих) - наподобие "Свободы слова" или "К барьеру", либо сокращать количество участников. Ибо дискуссия во время лекции или после оной - это балаган (тем более в отсутствии ведущего).&lt;/li&gt;    &lt;li&gt;Тема должна быть более конкретной и конструктивной. А не: "Я тут вам сейчас расскажу что-нибудь (а вы подеретесь из-за деталей, о которых я намеренно не говорил)". Кто-то может поделиться опытом? Пожалуйста! Только нужно рассказывать об условиях, в которых этот опыт получен: размер проектной команды, ее квалификация, сложность проекта, каков заказчик (как он участвует в процессе), есть ли бюрократия и т.п. - с деталями (ведь именно они зачастую и делают разницу между номинально похожими проектами колоссальной).&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;С одной стороны, конечно, приятно, что в городе есть какое-то публичное общество разработчиков, организуются какие-то мероприятия, но осадок остается после каждой такой встречи - время было потеряно впустую (ну или почти впустую). Вспоминается фраза из одной песни: "Иди тусуйся!" - в этом по-моему (на данном этапе) и заключается суть мероприятия. А могло бы быть полезным. Хочется надеяться таким оно когда-нибудь станет (если кризис нас всех не прикончит ;-) ).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-1658691089521780342?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/1658691089521780342/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2008/11/4-it-talk.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1658691089521780342'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1658691089521780342'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2008/11/4-it-talk.html' title='4я встреча IT Talk в Харькове'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-6011605387342657939</id><published>2008-11-11T00:28:00.001+02:00</published><updated>2008-11-11T00:28:08.887+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Разное'/><category scheme='http://www.blogger.com/atom/ns#' term='dbbuilder'/><title type='text'>svn:keywords и Unicode</title><content type='html'>&lt;p&gt;Давно хотел, чтобы все было как у людей: в каждом файле в заголовке - номер последней ревезии, в которой этот файл изменялся, автор изменений и дата. Такой заголовок особенно полезен для sql - скриптов, которые создают хранимые процедуры и пользовательские функции (в общем: логику), ибо позволяет заглянув в базу сразу же узнать кто и когда менял тот или иной объект.&lt;/p&gt;  &lt;p&gt;Для тех, кому повезло использовать SVN для контроля за версиями (мне, как раз повезло), есть, казалось бы, простой и безболезненный способ - &lt;a href="http://svnbook.red-bean.com/nightly/en/svn.advanced.props.special.keywords.html" target="_blank"&gt;svn:keywords&lt;/a&gt;. Выставляем для sql (или любого другого) файла это свойство (см. руководство по TortoiseSVN &lt;a href="http://tortoisesvn.net/docs/release/TortoiseSVN_ru/tsvn-dug-propertypage.html" target="_blank"&gt;здесь&lt;/a&gt;), и в файле используем ключевые слова, например так:&lt;/p&gt;  &lt;p&gt;-- $Rev$&lt;/p&gt;  &lt;p&gt;-- $Author$&lt;/p&gt;  &lt;p&gt;CREATE PROCEDURE ...&lt;/p&gt;  &lt;p&gt;и вот оно счастье...&lt;/p&gt;  &lt;p&gt;Но не тут-то было! Проблема появляется оттуда, откуда ее никто не ждал. SQL Server Management Studio по-умолчанию сохраняет все файлы в формате Unicode. Причем не просто Unicode, а UTF-16 с &lt;a href="http://en.wikipedia.org/wiki/Byte-order_mark" target="_blank"&gt;byte-order mark&lt;/a&gt;. Точно также поступают и некоторые библиотеки (в частности SMO). А SVN помечает файлы в этой кодировке как имеющие mime-type: octet-stream/application - т.е. фактически считает их бинарными и, соответственно, никакой подстановки ключевых слов не делает. Обидно, тем более, что файл-то текстовый.&lt;/p&gt;  &lt;p&gt;Поиск в &lt;a href="http://www.google.com/search?q=svn+unicode" target="_blank"&gt;Google&lt;/a&gt; дает мало толковых результатов: да, есть такая проблема, можно попытаться &lt;a href="http://www.cims.nyu.edu/~leingang/content/subversion-keywords-and-unicode" target="_blank"&gt;обойти&lt;/a&gt; (и то, сайт недоступен, можно почитать только из кэша Google). И только покопавшись еще глубже, можно обнаружить &lt;a href="http://www.nabble.com/Unicode,-auto-props-and-eol-style-td16218995.html" target="_blank"&gt;вот это&lt;/a&gt;: SVN поддерживает Unicode, но текстовым будет считаться только файл в кодировке UTF-8 (а не UTF-16 или UTF-32) - соответсвенно и ключевые слова будут работать только если файл в одной из однобайтовых кодировок (верх &amp;quot;крутизны&amp;quot; - та самая UTF-8).&lt;/p&gt;  &lt;p&gt;Ну лучше уж так, чем никак. В результате: &lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;быстренько сделал утилиту по переконвертированию файлов в кодировку UTF-8 (зачем пользоваться shareware, если самом сделать - дело 5ти минут? ;) ). Исполняемый файл &lt;a href="http://dbbuilder.googlecode.com/files/text2utf8.exe" target="_blank"&gt;здесь&lt;/a&gt;, исходник (если кому интересно) &lt;a href="http://dbbuilder.googlecode.com/files/text2utf8-src.zip" target="_blank"&gt;здесь&lt;/a&gt;. &lt;/li&gt;    &lt;li&gt;сделал так, что &lt;a href="http://code.google.com/p/dbbuilder/" target="_blank"&gt;dbbuilder&lt;/a&gt; теперь сохраняет все скрипты в кодировке UTF-8&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Надеюсь кому-нибудь поможет.&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;  &lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-6011605387342657939?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/6011605387342657939/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2008/11/svnkeywords-unicode.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/6011605387342657939'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/6011605387342657939'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2008/11/svnkeywords-unicode.html' title='svn:keywords и Unicode'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-1460548172726448891</id><published>2008-11-08T23:37:00.002+02:00</published><updated>2008-12-01T00:30:32.045+02:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='SMO'/><title type='text'>Пару слов о Sql Server Management Objects (SMO) (v.9 и v.10)</title><content type='html'>&lt;p&gt;На днях повысил версию &lt;a href="http://msdn.microsoft.com/en-us/library/ms162169.aspx" target="_blank"&gt;SMO&lt;/a&gt;. Причин на то было две: категорически не устраивала работа SMO v.9 с полнотекстовыми индексами и хотелось добавить поддержку SQL Server 2008 (что в любом случае требовало SMO v.10). Но обо всем по-порядку. &lt;/p&gt;  &lt;p&gt;Когда появилась настоятельная необходимость добавить в &lt;a href="http://code.google.com/p/dbbuilder/" target="_blank"&gt;dbbuilder&lt;/a&gt; поддержку полнотекстовых объектов (каталогов и индексов), то первым (вполне очевидным) решением было обратиться к SMO и при генерации скриптов сохранять еще пару типов объектов. При создании базы проблем возникнуть не должно было. Но на деле оказалось, что в SMO v.9 есть такая "особенность": при создании скрипта для полнотекстового индекса в него &lt;strong&gt;&lt;em&gt;всегда&lt;/em&gt;&lt;/strong&gt; добавляется USING [&amp;lt;исходная БД&amp;gt;]. Причем эта "фича" гнездится в самом коде SMO (спасибо, &lt;a href="http://www.red-gate.com/products/reflector/" target="_blank"&gt;Reflector&lt;/a&gt;) и на нее не влияют ScriptingOptions равно как и порядок создания скриптов. В результате мы всегда получаем скрипты, которые непригодны к использованию сразу после генерации и требуют ручной "доработки напильником" (удаления USING), что нехорошо. &lt;/p&gt;  &lt;p&gt;Поругавшись про себя решил посмотреть: а как обстоит дело в SMO v.10? Оказалось что хорошо обстоит: баг (или фичу) починили (или убрали) и теперь скрипты получаются такие, какие и дОлжно. В ходе тестовых запусков обнаружил, что задачи по генерации скриптов/созданию базы стали выполняться быстрее. Решил проверить и с еще большим удивлением обнаружил, что таки да, таки что-то произошло: SMO v.10 работает значительно быстрее v.9. Сравнительные (усредненные по двум запускам) результаты для некоторой базы данных (141 таблица, 194 хранимых процедуры, 2 триггера, 10 пользовательских функций и 5 представлений) приведены в таблице ниже.&lt;/p&gt;  &lt;table cellspacing="0" cellpadding="2" width="550" border="0"&gt;&lt;tbody&gt;     &lt;tr&gt;       &lt;td valign="top" width="351"&gt; &lt;/td&gt;        &lt;td valign="top" width="100"&gt;SMO v.9&lt;/td&gt;        &lt;td valign="top" width="97"&gt;SMO v.10&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="351"&gt;Создание новой (пустой) базы&lt;/td&gt;        &lt;td valign="top" width="100"&gt;0:24&lt;/td&gt;        &lt;td valign="top" width="100"&gt;0:20&lt;/td&gt;     &lt;/tr&gt;      &lt;tr&gt;       &lt;td valign="top" width="351"&gt;Генерация скриптов&lt;/td&gt;        &lt;td valign="top" width="100"&gt;8:35&lt;/td&gt;        &lt;td valign="top" width="103"&gt;0:58&lt;/td&gt;     &lt;/tr&gt;   &lt;/tbody&gt;&lt;/table&gt;  &lt;p&gt;Признаюсь: меня сильно впечатлило увеличение скорости генерации скриптов почти на порядок.&lt;/p&gt;  &lt;p&gt;А теперь ложка дёгтя: SMO как-то очень забавно работает с SQL Server Express. Если я попытаюсь через SMO обратиться к полнотектовому каталогу базы данных, работающей на SQL Server Express with Advanced Services (в котором есть Full-text search), то получу исключение "SQL Server Express не поддерживает Full-Text". Отлично, просто отлично! Но этот самый Full-text, к счастью, работает вне зависимости от того, что на этот счет "думает" SMO.&lt;/p&gt;  &lt;p&gt;Надеюсь кому-то поможет.&lt;/p&gt;  &lt;p&gt;AlexS&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-1460548172726448891?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/1460548172726448891/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2008/11/sql-server-management-objects-smo-v9.html#comment-form' title='Комментарии: 0'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1460548172726448891'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1460548172726448891'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2008/11/sql-server-management-objects-smo-v9.html' title='Пару слов о Sql Server Management Objects (SMO) (v.9 и v.10)'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6076140503946495214.post-1430871644329833015</id><published>2008-11-08T00:44:00.004+02:00</published><updated>2008-11-08T12:43:35.423+02:00</updated><title type='text'>Начало</title><content type='html'>Сегодня я решил положить начало блоговой эры в моей жизни. Формальным поводом стало решение создать проект с открытым исходным кодом - &lt;a href="http://code.google.com/p/dbbuilder/"&gt;dbbuilder&lt;/a&gt;. Хотя и без этого в последнее время у меня периодически возникает желание записать что-то, с чем приходится сталкиваться в своей профессиональной деятельности - для себя (для истории), ну и может еще кому-нибудь пригодится.&lt;div&gt;&lt;br /&gt;&lt;div&gt;Так что начнем.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6076140503946495214-1430871644329833015?l=dbbuilder.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://dbbuilder.blogspot.com/feeds/1430871644329833015/comments/default' title='Комментарии к сообщению'/><link rel='replies' type='text/html' href='http://dbbuilder.blogspot.com/2008/11/blog-post.html#comment-form' title='Комментарии: 2'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1430871644329833015'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6076140503946495214/posts/default/1430871644329833015'/><link rel='alternate' type='text/html' href='http://dbbuilder.blogspot.com/2008/11/blog-post.html' title='Начало'/><author><name>AlexS</name><uri>http://www.blogger.com/profile/16426202835041321615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry></feed>
