Этот уютный бложек будет посвящен небольшим и небезынтересным для меня находкам на ниве кодинга.
Я снова здесь.
Нередко при создании хранимых процедур, вставляющих или апдейтящих данные возникает проблема преобразования конструкции типа
(это перечисление переменных на Insert)
к виду, необходимому на Update:
В простейшем случае это можно сделать и руками, при помощи копипасты. Но сейчас мне понадобилось провести эту операцию для таблицы, имеющей 24 поля. Поэтому пришлось обратиться к такому редко используемому средству обработки текста в IDE от Microsoft, как регулярные выражения. Проблема решилась следующим образом: в окне Find and Replace 1. в Find what вводится: \@{(.*)}\n
2. в Replace with вводится просто: \1 = \0
Нередко при создании хранимых процедур, вставляющих или апдейтящих данные возникает проблема преобразования конструкции типа
,@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)
Вполне работает.
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#
Implementing the Singleton Pattern in C#
yield операторы, на мой взгляд - одно из самых прекрасных нововведений в C# 2.0 . Хотя есть информация, что для программистов Ruby и Python такой подход не внове.
Что такое yield return? Кратко - возврат результата без потери контекста функции. При этом на базе функции автоматически создается итератор, который будет выполнять необходимый код при каждом переходе к следующему элементу коллекции.
Сейчас я чувствую себя слишком усталым для подробного теоретического раскрытия темы, поэтому приведу лучше пару примеров, реализованных при помощи этого оператора:
Пример 1. Итерация по нескольким коллекциям без создания промежуточной коллекции:
Вызов этой функции для коллекций:
Даст следующий результат:
a1
a2
a3
b1
b2
c1
c2
c3
c4
Пример 2. "Отложенная сортировка"
Что такое 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'а является кластеризованным индексом - обычным, наверное, тоже может быть неплохо - но на кластеризованном мы получаем впечатляющие результаты.
Но этим ли исчерпывается наше стремление к совершенству?
Красиво выглядит такая конструкция, не правда ли?
Да и скорость впечатляет, потому что здесь также применяется clustered index scan.
Но, к сожалению, в этом бочонке меда не без ложечки дегтя.
Во-первых, нужно вручную подбирать делитель, остаток от которого мы используем, для определения среднего размера "шага" по выборке.
Во-вторых, что более важно, этот запрос содержит ошибку.
Если запустить его на тестовом наборе данных размером 7 млн. записей, например, обнаружится, что в выборку попадут только записи из первых 900 тысяч.
А как же остальные?
А они не попали в выборку, потому что требуемое число записей неизбежно накапливается раньше полного прохода.
По условию же задачи нам нужны случайные элементы из всей таблицы.
Но этот вопрос решаемый:
Вуаля! Наш запрос ничем не уступает по скорости варианту два, но при этом гораздо лаконичнее.
Похоже, мы практически познали дао случайной выборки.
В предыдущих выпусках были рассмотрены два способа получить случайную выборку из таблицы.
Напомню, самый распространенный (и неправильный для больших объемов данных) - 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
Вуаля! Наш запрос ничем не уступает по скорости варианту два, но при этом гораздо лаконичнее.
Похоже, мы практически познали дао случайной выборки.
Когда я устанавливал на sql сервер свою сборку, всплыл еще один забавный факт.
Сборка ставилась с уровнем доступа SAFE, а этот уровень доступа не позволяет использовать статические переменные класса, если они не объявлены как readonly. И я получил следующее сообщение:
Конструктор, однако, содержал определение анонимного делегата - приблизительно такого вида:
1. Это само тело определенного нами анонимного метода. Пока ничего военного - статические методы использовать можно.
[CompilerGenerated]
3. Ну и при просмотре кода конструктора в режиме IL наши опасения полностью подтверждены:
Так то!
Сборка ставилась с уровнем доступа 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]2.А это что? Без нашего на то желания компилятор сгенерил нам статическое поле класса.
private static int <.ctor>b__0()
{
return 0;
}
[CompilerGenerated]
private static SomeDelegate <>9__CachedAnonymousMethodDelegate1;
3. Ну и при просмотре кода конструктора в режиме IL наши опасения полностью подтверждены:
ldsfld class SomeNS.SomeDelegate SomeNS.XClass::<>9__CachedAnonymousMethodDelegate1
Так то!
Вот здесь мне справедливо указали на недостаток этого распространенного решения.
Действительно, оно далеко не оптимально по производительности.
( а вот кое-что побыстрее )
Здесь меня смущает два фактора: во-первых некоторая тяжеловесность всей конструкции, требующая помещения в отдельную сторед процидурку, во-вторых - необходимость сортировки для получения Row_Number().
Тем не менее время выборки на таблице в 7 млн. записей - в пять раз меньше, что для исходного варианта.
UPD: Самый эффективный на сегодняшний момент вариант:
Здесь id - GUID.
UPD1: С предыдущим примером тоже есть свои проблемы...
Действительно, оно далеко не оптимально по производительности.
( а вот кое-что побыстрее )
Здесь меня смущает два фактора: во-первых некоторая тяжеловесность всей конструкции, требующая помещения в отдельную сторед процидурку, во-вторых - необходимость сортировки для получения 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: С предыдущим примером тоже есть свои проблемы...
Полна чудес могучая природа,
Как говорил товарищ Берендей
(Ерофеев)
Намедни столкнулся с парадоксальной ситуацией, до сих пор не могу понять механизм происходящего.
Итак, есть таблица в БД, которую нужно заполнить N-ным количеством записей для тестирования.
Есть некая худо-бедно работающая утилитка, занимающаяся этим делом. У нее есть существенный недостаток - она медленная. Пока речь шла о тысячах записей, она вполне справлялась. Но есть необходимость проверить для большого их числа. Порядка миллионов.
Поскольку 99% времени уходит вызов stored procedure на сервере, возникает следующая идея: а что, если сделать добавление записей в контексте SQL SERVER? Теоретически, ускорение должно быть существенное. А на практике...
Итак, функционал перенесен в отдельную сборку, залитую на сервер. Почему не на T-SQL? Просто запись создается по достаточно сложным закономерностям, такое проблематично написать на sql, да и зачем, когда есть готовая процедурка. Крутится цикл, в нем генерируются параметры для другой stored процедуры, которая, собственно, вставляет записи в табличку. Тестируем... profit?
Нет. Проход тестового цикла на сервере (с контекстным соединением) на порядок медленее , чем то же самое в отдельном приложении. Причем установлено, что основные тормоза связаны именно с исполнением SQlCommand в серверном контексте.
Почему так? Есть жизнь на Марсе, нет жизни на Марсе - науке пока неизвестно...
Как говорил товарищ Берендей
(Ерофеев)
Намедни столкнулся с парадоксальной ситуацией, до сих пор не могу понять механизм происходящего.
Итак, есть таблица в БД, которую нужно заполнить N-ным количеством записей для тестирования.
Есть некая худо-бедно работающая утилитка, занимающаяся этим делом. У нее есть существенный недостаток - она медленная. Пока речь шла о тысячах записей, она вполне справлялась. Но есть необходимость проверить для большого их числа. Порядка миллионов.
Поскольку 99% времени уходит вызов stored procedure на сервере, возникает следующая идея: а что, если сделать добавление записей в контексте SQL SERVER? Теоретически, ускорение должно быть существенное. А на практике...
Итак, функционал перенесен в отдельную сборку, залитую на сервер. Почему не на T-SQL? Просто запись создается по достаточно сложным закономерностям, такое проблематично написать на sql, да и зачем, когда есть готовая процедурка. Крутится цикл, в нем генерируются параметры для другой stored процедуры, которая, собственно, вставляет записи в табличку. Тестируем... profit?
Нет. Проход тестового цикла на сервере (с контекстным соединением) на порядок медленее , чем то же самое в отдельном приложении. Причем установлено, что основные тормоза связаны именно с исполнением SQlCommand в серверном контексте.
Почему так? Есть жизнь на Марсе, нет жизни на Марсе - науке пока неизвестно...
- Mood:озадаченое
Вполне достаточно:
SELECT * FROM SOME_TABLE ORDER BY NEWID()
или
SELECT * FROM SOME_TABLE ORDER BY RAND()
Используя TOP X можно выбрать заданное число записей случайным образом.
(Все, как всегда, изобрели до нас. Подробней по теме.)
SELECT * FROM SOME_TABLE ORDER BY NEWID()
или
SELECT * FROM SOME_TABLE ORDER BY RAND()
Используя TOP X можно выбрать заданное число записей случайным образом.
(Все, как всегда, изобрели до нас. Подробней по теме.)
