Home

Advertisement

О журнале

  • Aug. 16th, 2018 at 12:26 AM
Этот уютный бложек будет посвящен небольшим и небезынтересным для меня находкам на ниве кодинга.
Я снова здесь.

Нередко при создании хранимых процедур, вставляющих или апдейтящих данные возникает проблема преобразования конструкции типа


,@ResourcesTitanium
,@ResourcesSilicon
,@ResourcesAM


(это перечисление переменных на Insert)

к виду, необходимому на Update:


ResourcesTitanium = @ResourcesTitanium
,ResourcesSilicon = @ResourcesSilicon
,ResourcesAM = @ResourcesAM


В простейшем случае это можно сделать и руками, при помощи копипасты. Но сейчас мне понадобилось провести эту операцию для таблицы, имеющей 24 поля. Поэтому пришлось обратиться к такому редко используемому средству обработки текста в IDE от Microsoft, как регулярные выражения. Проблема решилась следующим образом: в окне Find and Replace 1. в Find what вводится: \@{(.*)}\n
2. в Replace with вводится просто: \1 = \0
В данный момент разрабатываю небольшую подсистему логирования. Возникла проблема - при много поточной записи в файл лога легко возможна ситуация с конфликтом доступа:

IO exception while trying to write into file log:The process cannot access the file bla-bla-bla

Есть стандартное решение проблемы - добавить объект для блокировки:

private object _fileLock = new object();
...
lock(_fileLock){...}
Но, оказывается, в данном случае можно обойтись и без него. Дело в том, что в .NET строка - immutable object, то есть две строки с одинаковым значением всегда ссылаются на один и тот же объект. Поэтому
lock (CurrentFileName)
Вполне работает.
Старенькая, но далеко не потерявшая ещё актуальности статья о реализации потоково-безопасных синглтонов:

Implementing the Singleton Pattern in C#
yield операторы, на мой взгляд - одно из самых прекрасных нововведений в C# 2.0 . Хотя есть информация, что для программистов Ruby и Python такой подход не внове.
Что такое yield return? Кратко - возврат результата без потери контекста функции. При этом на базе функции автоматически создается итератор, который будет выполнять необходимый код при каждом переходе к следующему элементу коллекции.
Сейчас я чувствую себя слишком усталым для подробного теоретического раскрытия темы, поэтому приведу лучше пару примеров, реализованных при помощи этого оператора:

Пример 1. Итерация по нескольким коллекциям без создания промежуточной коллекции:

private IEnumerable JoinCollections(params IEnumerable[] colls)
{
foreach (IEnumerable col in colls)
{
foreach (object obj in col)
{
yield return obj;
}
}
yield break;
}


Вызов этой функции для коллекций:
string[] a = new string[] { "a1", "a2", "a3" };
string[] b = new string[] { "b1", "b2"};
string[] c = new string[] { "c1", "c2","c3","c4" };

foreach (string s in JoinCollections(a, b, c))
{
System.Diagnostics.Trace.WriteLine(s);

}


Даст следующий результат:
a1
a2
a3
b1
b2
c1
c2
c3
c4

Пример 2. "Отложенная сортировка"

код.. )


    
Вновь на тему.
В предыдущих выпусках были рассмотрены два способа получить случайную выборку из таблицы.
Напомню, самый распространенный (и неправильный для больших объемов данных) - ORDER BY NEWID(). Это не мудренно, так как подразумевается ПОЛНАЯ сортировка таблицы. EPIC FAIL, если число записей исчисляется миллионами.
Затем идет способ с генерацией временной таблицы случайных id и join'ом на эту таблицу. Этот способ существенно быстрее, но несколько громоздок в исполнении. Преимущество он дает в том случае, когда поле join'а является кластеризованным индексом - обычным, наверное, тоже может быть неплохо - но на кластеризованном мы получаем впечатляющие результаты.
Но этим ли исчерпывается наше стремление к совершенству?
Красиво выглядит такая конструкция, не правда ли?
select top 50000 * from dbo.test1 where id % 10 = checksum(newid()) % 10

Да и скорость впечатляет, потому что здесь также применяется clustered index scan.
Но, к сожалению, в этом бочонке меда не без ложечки дегтя.
Во-первых, нужно вручную подбирать делитель, остаток от которого мы используем, для определения среднего размера "шага" по выборке.
Во-вторых, что более важно, этот запрос содержит ошибку.
Если запустить его на тестовом наборе данных размером 7 млн. записей, например, обнаружится, что в выборку попадут только записи из первых 900 тысяч.
А как же остальные?
А они не попали в выборку, потому что требуемое число записей неизбежно накапливается раньше полного прохода.
По условию же задачи нам нужны случайные элементы из всей таблицы.
Но этот вопрос решаемый:

declare @count int
set @count = (select count(*) from dbo.test1)

declare @step int
set @step = @count/50000;

select top 50000 * from dbo.test1 where id % @step = checksum(newid()) % @step


Вуаля! Наш запрос ничем не уступает по скорости варианту два, но при этом гораздо лаконичнее.

Похоже, мы практически познали дао случайной выборки.

Tags:

Когда я устанавливал на sql сервер свою сборку, всплыл еще один забавный факт.
Сборка ставилась с уровнем доступа SAFE, а этот уровень доступа не позволяет использовать статические переменные класса, если они не объявлены как readonly. И я получил следующее сообщение:

"CREATE ASSEMBLY failed because method '.ctor' on type 'StoredProcedures' in
safe assembly 'SomeAssembly' is storing to a static field. Storing to a static
field is not allowed in safe assemblies."

Проблема же была в том, что класс не использовал не одного статического поля.
Конструктор, однако, содержал определение анонимного делегата - приблизительно такого вида:

delegate int SomeDelegate();

class XClass
{

public XClass()
{
SomeDelegate myDelegate = delegate
{
return 0;
};

RegisterDelegate(myDelegate);
}
...
}
Использование рефлектора подтверждает все догадки:
1. Это само тело определенного нами анонимного метода. Пока ничего военного - статические методы использовать можно.
[CompilerGenerated]
private static int <.ctor>b__0()
{
return 0;
}
2.А это что? Без нашего на то желания компилятор сгенерил нам статическое поле класса.
[CompilerGenerated]
private static SomeDelegate <>9__CachedAnonymousMethodDelegate1;

3. Ну и при просмотре кода конструктора в режиме IL наши опасения полностью подтверждены:
ldsfld class SomeNS.SomeDelegate SomeNS.XClass::<>9__CachedAnonymousMethodDelegate1


Так то!
Вот здесь мне справедливо указали на недостаток этого распространенного решения.
Действительно, оно далеко не оптимально по производительности.

а вот кое-что побыстрее )

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

UPD: Самый эффективный на сегодняшний момент вариант:


declare @rnd int;
set @rnd = checksum(newid()) % 10

select top 10000 * from dbo.test1 where checksum(id) % 10 = @rnd


Здесь id - GUID.

UPD1: С предыдущим примером тоже есть свои проблемы...

Tags:

[SQL Server 2005] CLR and WTF?!

  • Aug. 16th, 2008 at 12:56 AM
Полна чудес могучая природа,
Как говорил товарищ Берендей
(Ерофеев)

Намедни столкнулся с парадоксальной ситуацией, до сих пор не могу понять механизм происходящего.
Итак, есть таблица в БД, которую нужно заполнить N-ным количеством записей для тестирования.
Есть некая худо-бедно работающая утилитка, занимающаяся этим делом. У нее есть существенный недостаток - она медленная. Пока речь шла о тысячах записей, она вполне справлялась. Но есть необходимость проверить для большого их числа. Порядка миллионов.
Поскольку 99% времени уходит вызов stored procedure на сервере, возникает следующая идея: а что, если сделать добавление записей в контексте SQL SERVER? Теоретически, ускорение должно быть существенное. А на практике...
Итак, функционал перенесен в отдельную сборку, залитую на сервер. Почему не на T-SQL? Просто запись создается по достаточно сложным закономерностям, такое проблематично написать на sql, да и зачем, когда есть готовая процедурка. Крутится цикл, в нем генерируются параметры для другой stored процедуры, которая, собственно, вставляет записи в табличку. Тестируем... profit?
Нет. Проход тестового цикла на сервере (с контекстным соединением) на порядок медленее , чем то же самое в отдельном приложении. Причем установлено, что основные тормоза связаны именно с исполнением SQlCommand в серверном контексте.

Почему так? Есть жизнь на Марсе, нет жизни на Марсе - науке пока неизвестно...

 
Вполне достаточно:

SELECT * FROM SOME_TABLE ORDER BY NEWID()
или
SELECT * FROM SOME_TABLE ORDER BY RAND()

Используя TOP X можно выбрать заданное число записей случайным образом.

(Все, как всегда, изобрели до нас. Подробней по теме.)