среда, 1 декабря 2010 г.

“В жизни всегда есть место для подвига” © или как реализовать Singleton по-новому

Тема реализации шаблона Singleton так или иначе затрагивает жизнь любого разработчика – толи на собеседовании спросят (или самом спросить придется), толи в реальном проекте найдется применение (не приведи Господи). О том, как этого зверя вырастить, написано немало – и в MSDN, и в прочих интернетах (последняя статься считается канонической и ранее располагалась по другому адресу). В какой-то момент казалось (мне по крайней мере), что придумать что-нибудь новое в этой области невозможно. Ошибся.

Недавно на одном из проектов заказчик прислал кусок [псевдо]кода, в котором кроме всего прочего содержалась крайне любопытная реализация привычного шаблона (тот еще затейник – архитектура решения тоже заставляем “много думать”). В чистом виде (убирая конкретику проекта и применяя привычные для .NET схемы именования и форматирования) это выглядит так:





public interface ISingleton
{

}

class Singleton : ISingleton
{
public delegate ISingleton InstanceGetterDelegate();

private static Singleton _singleton;
private static InstanceGetterDelegate _instanceGetter;
static Singleton()
{
var type = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;
// assign the closure that executes on the first call of GetInstance() method
_instanceGetter = () =>
{
Singleton instance = null;
lock (type)
{
var rslt = Interlocked.CompareExchange<Singleton>(ref _singleton, null, null);
if (rslt == null) // indicates is first call
{
instance = new Singleton(/* init arguments go here*/);
Interlocked.CompareExchange<Singleton>(ref _singleton, null, instance);

// now replace with a closure that executes on all subsequent calls of GetInstance() method
InstanceGetterDelegate fn = () =>
{
return Interlocked.CompareExchange<Singleton>(ref _singleton, null, null);
};
Interlocked.Exchange<InstanceGetterDelegate>(ref _instanceGetter, fn);
}
else
{
instance = rslt; // was already set with singleton instance
}
}
return instance;
};
}
private Singleton() { } // disable default instance constructor
public static ISingleton GetInstance() { return _instanceGetter(); }
}


И вот тут, что называется, “накрывает”. Я бы назвал этот способ “JavaScript-style Singleton” (конечно же за использования замыканий). В целом (за исключение блокировки по Type (о чем я уже писал) и использования рефлексии в статическом конструкторе) реализация выглядит любопытной и уж во всяком случае расширяет горизонт.



HTH, AlexS

суббота, 27 ноября 2010 г.

sqlite: приятные мелочи в области агрегирующих функций

Продолжаю очень приятно удивляться sqlite. Очередным поводом стали агрегирующие функции:

  • есть две функции для вычисления сумм: традиционная sum() и чуть менее традиционная total() – обе суммируют не-NULL значения, но первая (в соответствии со стандартом SQL) возвращает пустой набор данных в случае, если таких значений не было, а вот вторая всегда возвращает 0.0 (что в некоторых случаях может быть весьма удобно). Кроме того, функция total() всегда возвращает double и не подвержена арифметическому переполнению;
  • есть крайне полезная функция group_concat(field[, separator]), которая объединяет значения поля в одну строку. Разделитель по-умолчанию – запятая. Причем в варианте с разделителем по-умолчанию можно использовать еще и DISTINCT:
    SELECT group_concat(DISTINCT user_name) FROM …. GROUP BY …
    Замечу, что в SQL Server-е (горячо и искренне мною любимом) нет простого способа сделать тоже самое – только через custom aggregate (т.е. специальную сборку, которую писать/разворачивать).

В общем: чем больше смотрю - тем больше нравится.

HTH, AlexS

вторник, 16 ноября 2010 г.

Эволюция MS Full-Text Search: доступ к анализатору полнотекстовых запросов

Так и работает эволюция: незаметно и по чуть-чуть. Недавно на StackOverflow задали вопрос (в вольном переводе):

Можно ли получить доступ к стеммеру, который используется в SQL Server FullText Search?

Моей первой мыслью было “нельзя”. Но на всякий случай решил свериться с MSDN. Так вот оказалось, что “почти можно”: в SQL Server 2008 в FullText Search помимо радикального изменения архитектуры, внесли менее заметные, но полезные изменения. В частности, была добавлена следующая системная табличная функция:

sys.dm_fts_parser('query_string', lcid, stoplist_id, accent_sensitivity)

Она-то и позволяет обратиться к стеммеру (точнее к синтаксическому анализатору полнотекстовых запросов). Вот как это работает (лучше один раз увидеть, как говорится):

select * from sys.dm_fts_parser('FORMSOF(inflectional, Worked)', 1033, 0, 0)

Т.е. мы запросили все словоформы слова “worked” в английской локали (locId = 1033). И вот какие ключевые слова будут соответствовать этому запросу:

image

Было бы еще хорошо иметь возможность заглянуть внутрь самого индекса (как это делает Luke для Lucene), но даже с этими средствами жить  становится намного веселее.

HTH, AlexS

четверг, 29 июля 2010 г.

Чем точнее указан путь – тем быстрее можно по нему идти ((С) Кэп)

Недавно попросили помочь с оптимизацией обработки XML в SQL Server-е. Запрос выглядит примерно следующим образом:

SELECT
  nref.value('./../../@id', 'int') AS articleid,
  nref.value('type[1]', 'nvarchar(256)') AS type,
  nref.value('theme[1]', 'varchar(256)') AS theme,
  nref.value('score[1]', 'varchar(32)') AS score
INTO #tmpTheme
FROM @bulk_xml.nodes('//theme') AS R(nref)

Очень простое действие – указание точного XPath выражения вместо обобщенного ('//theme') позволяет ускорить работу данного запроса в 1.5 – 2 раза.

HTH,

AlexS

воскресенье, 11 июля 2010 г.

Заблуждения: “restore from database”

Недавно пришлось развенчивать миф: “restore from database” (см. скриншот) позволяет использовать журнал транзакций [базы данных А] для “point in time recovery” [базы банных Б, восстановленной из резервной копии базы данных А].

image

Контекст, в котором происходило обсуждение, тоже представляет определенный интерес Есть база данных [пусть будет “База А”], которая довольно сильно нагружена. Она работает в режиме восстановления от сбоев “Full”. Каждую ночь делается полный бэкап этой базы, никакие другие бэкапы (дифференциальный, журнала) не делаются. Совет перевести базу в режим восстановления “Simple” (нагрузка на подсистему ввода/вывода меньше, размер журнала меньше, по возможностям  восстановления ничего не теряем) наталкивается на следующий аргумент  (вольный перевод)

“если База А будет повреждена, то мы сможем восстановить ее из резервной копии и донакатить транзакции из журнала, поэтому Simple нам не подходит”

Так вот, “эта штука так не работает”. Опция “from database” просто-напросто подхватывает историю резервных копий выбранной базы данных – так что пользователю не нужно лазить по дискам в поисках какого-то конкретного бэкапа (или самого свежего бэкапа). Журнал транзакций “намертво” привязан к базе и только SQL Server может что-то с ним делать (пользователь может только посмотреть что в нем, да и то используя недокументированные функции). Единственный [для пользователя] способ использовать информацию об операциях, хранящуюся в журнале, – сделать резервную копию журнала после создания полного бэкапа базы данных. В этом случае резервная копия журнала (а не сам журнал!) может быть использована для повторного выполнения операций в базе, восстановленной из этого полного бэкапа (т.е. “point in time recovery”).

HTH, AlexS

воскресенье, 16 мая 2010 г.

Право на жизнь для табличных переменных

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

Недавно на StackOverflow был задан вопрос, который в вольном переводе звучит следующим образом: как мне сохранить какие-то данные из транзакции, которая затем будет отменена? Сценарий, который стоит за этом вопросом, более чем реален: протоколирование. Ответом на этот вопрос и являются табличные переменные: изменения значений переменных не является частью транзакции (и, соответственно, не подлежат отмене).

Проверил: да, таки работает. Но тут как в том старом анекдоте: “ложечки нашлись, но осадок остался”. Табличная переменная на самом деле – это временная таблица (что легко проверить). При этом изменения, вносимые в “обычные” временные таблицы протоколируются и являются частью транзакции, в которой эти изменения происходят (что тоже легко проверяется). Получается некоторое противоречие: табличная переменная – это временная таблица, но изменения, которые в нее вносятся не протоколируются. Покопавшись в гуглоридере, нашел (отмеченный мною же!) пост в блоге SQL Server Storage Engine на эту тему – “перечитал и переосмыслил” :-)

HTH

AlexS

вторник, 6 апреля 2010 г.

Бытовые особенности SQLite

В последние пару недель работаю с SQLite (не то чтобы очень плотно, но плотнее, чем “просто поковырять”). Штуковина безусловно полезная и нужная, но не без особенностей. Ниже приведу список того, обо что споткнулся сам:

  • Нет типа данных DateTime или чего-то похожего – все даты нужно хранить в виде строки; проблема мелкая, но стоит о ней помнить. Более подробное описание типов данных SQLite лежит здесь.
  • Нет функции поиска подстроки/символа в строке (т.е. ничего, аналогичного CHARINDEX в SQL Server-е). Можно, конечно, самому написать такую функцию (на C) и подключить ее на лету, но … в общем лучше бы она была сразу.
  • Не поддерживается [ставшая уже привычной] конструкция UPDATE|DELETE|INSERT …. FROM ….
  • Крайне любопытно организовано хранение данных, в частности – в каждой таблице неявно присутствует “автоматический первичный ключ” (поле ROWID), который к тому же является “кластерным” (в привычных терминах). Причем явное объявление “целочисленного, автоинкрементного первичного ключа” в большинстве случаев – просто псевдоним для ROWID.
  • Есть доступ к метаданным (их, правда, немного) – таблица sqlite_master.
  • Сравнение строк может производиться как с учетом регистра, так и без оного – зависит от того, как была собрана библиотека и от того, какие строки (ASCII/Unicode) сравниваются.
  • Поддерживаются триггеры, причем можно повесить триггер на обновление какой-то конкретной колонки.
  • Параметры могут определяться так же, как и в SQL Server-е - @ParamName (но поддерживаются и другие варианты: ?Name, :Name, $Name).

HTH,

AlexS