Rambler's Top100
Главная
Новости
Статьи
Форумы
Книги
Коды
Сообщество
Блоги
О нас
 

Логин

Email:
  Пароль:

Войти
Зарегистрироваться
Забыл пароль

Поиск

 Искать :
 
Вперед

Nik Legaloff Blog. Connection managment. Комментарии.

Connection managment

У всех своя стратегия управления соединениями.
Хочу поделиться своей.
Предпосылки
1. Web приложение
2. За время жизни формы на сервере часто нужны новые соединения.
3. Часто бывает что пока открыто одно соединение, и из него читаются данные, необходимо открывать другие соединения.
4. Допустим что приложение работает пока только с одной БД(один connectionString)

Самое простое решение - каждый раз открывать новое соединение.
ВОт пример.
public static SqlConnection GetNewConnection()
{
// просто каждый раз создаём новое соединение
SqlConnection conection = new SqlConnection(CStr);
return conection;
}
где CStr это кэшируемый connectionString взятый к примеру из config файла


/// <summary>
/// ConnectionString для текущего приложения указанный config файле
/// </summary>
public static string CStr
{
get
{

lock(cstrLock)
{
try
{
if (cstr == null)
{
AppSettingsReader reader = new AppSettingsReader();
cstr = reader.GetValue("ConnectionString", typeof(string)).ToString();
}
}
catch
{
cstr = "connection string по умолчанию";

}
return cstr;
}


}
}


т.е. просто взяли новый connection, поюзали, да главное не забыли его потом закрыть.

Аднака хоть соединения не создаются каждый раз по новой, а берутся из connectionPool которого нам не видно, но всё же это происходит долго.
Пример моей статистики. несколько десятков запросов, каждый создаёт соединение.
половина времени лежала на открытии нового соединения.

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

реализацию можно увидеть в примере кода

ЗЫ. Хранить экземпляр ConnectionPool можно в любом легкодоступном месте.
Я например его храню в DataSession(аналог Session). Это статическая коллекция экземпляров DataSession, каждый из которых привязывается к сессии клиента.
К тому же там удобно хранить экземпляр UnitOfWork или IdentityMap.
Ну нужно не забывать по окончанию реквеста их всех закрывать. Клиент то может не вернуться. Но это уже совсем другая история

Nik Legaloff

10 August 2005 19:27  Комментарии (10)

11 August 2005 11:48 Dimon aka Manowar

Хм... а в цифирях как оно будте выражаться?

Не пробовал погонять тесты и сравнить?
11 August 2005 13:09 Nik Legaloff

Цифры

Потестил, проверил.
т.е. было 2 случая.
1. Когда просто создавалось кажный раз новое соединение и отдавалось. Потом оно просто закрывалось.
2. Использовался описанный выше connectionPool

Тест состоял из одно выборки всех клиентов(их около 2000, 1 запрос)
При загрузке клиента, вместе с ним одним sql запросом загружались их договора.

Замеры производил ANTS профайлером
итак итоги.
1. 2029 раз запрашивалось новое соединение открывалось и закрывалось. На это ушло

Создание соединения 0,038 сек
Его открытие 0,5 сек.
Закрытие:0,025
Итого:0,56

2. Создавалось соединение 2 раза(пока открыто соединение с клиентами, открывается ещё 1 на загрузку его договоров)
Создание соединения: 0,02 сек
Открытие его 0,35.
все остальные движения незначимы и в сумме меньше 0,01 сек
Итого 0,38

Разница составляет 0,18 сек.(32%)

При этом время на загрузку всех клиентов составило 1,86 сек.
Разница в таком случае 10%


Да, это немного. Интересно почему тогда у меня были немного иные цифры, там выигрыш был порядка 20-40%
11 August 2005 13:19 Nik Legaloff

ЗЫ

Хочу ещё успокоить тех, кого испугала цифра 1,8 сек на загрузку 2000 обьектов.
Я временно сделал прелоад контрактов клиентов. В рабочем приложении всё загружается по требованию и оседает в кэше, который помаленьку избавляется от редковостребованных данных.
А так их загрузка занимает времени не многим больше чем просто select.
11 August 2005 17:09 Dimon aka Manowar

Да, резон использовать есть

Нужно будет прикинуть в своих BL насколько быстро это можно встроитьУлыбка И как с транзакциями получится совместить.
11 August 2005 18:38 Nik Legaloff

Ну так а в чём проблема? :)

А тебе какие нужны транзакции?
на уровне MSSQL или бизнесТранзакции?
БизнесТранзакции легко можно сделать, использую свою dataSession, в неё и connectionPool ляжет, туда же и UnitOfWork пойдёт. И потом на EndRequest делай DataSession.Commit() и в нём делай unitOfWork.commit() и для connectionPool.CloseAll()
так тебе не нужно будет заботится о том, что бы комитить бизнес транзакцию и высвобождать ресурсы соединений.

Ну а простые SQL транзакции легко ложатся на этот механизм.
Получил конекшин, начал транзакцию. Соединение то пойдёт в список активных, и никто его не получит, пока его не вернут. Закамитил или отменил транзакцию и возвращай соединение.

другое дело то что ты можешь представить конекшинПул как SQLSource
и у него можешь запрашивать как конекшин, так и транзакцию.
Запрос конекшина останется таким же(GetConnection()), а вот при запросе транзакции (GetTransaction(params)) брать конекшин(GetConnection()) и у него начинать транзакцию BeginTransaction(params)

а можно и их аккуратно обернуть в свой типУлыбка
15 August 2005 15:57 Dimon aka Manowar

С транзакциями оказалось просто

Просто-напросто Begin/End вызывается ужен из полученного соединения, которое потм и тащится по всем запросам. Но вылезла другая гадость - у меня например частенько методы DAL/BLL возвращают IDataReader открытый с CommandBehaviour.CloseConnection, и тут уж никак коннекцию в пул просто не вернуть, ибо потом я оперирую только ридером, который закрываясь сам закрывает и коннекцию. Так что в этом случае твоя идея трудноприменима (разве что переделывать методы с ридерами на типизированне коллекцииУлыбка).

Ну и по поводу хранения - как по мне, так синглтон будет самое то. И не нужно придумывать дополнительно ничего о местах хранения.
17 August 2005 18:19 Nik Legaloff

Ну так вернёт он тебе закрытый конекшин

Ну и что, он ляжет в закрытые, и при следующем запросе если не будет открытых, то пул возьмёт соединение из закрытых и открыв его по новой - вернёт клиенту.
Проблемы тут НИКАКОЙ нет.

Ну а по поводу синглетона... конечно так уж получается что для web синглетон очень превликателен.
Есть только одно но, конекшин пул станет доступен для разных клиентов одновременно, тут придётся расставить блокировки, что бы не было споров за ресурс.
В датаСесии каждому по экземпляру датаСесии. и свой конекшин пул.
к тому же по окончанию запроса все соединения закрываются и высвобождаются.
Хотя с другой стороны один общий пул тоде превликателен и как бы экономичен. а то что твой приложение будет держат открытые соединения наготове постоянно, то это тоже хорошо. в общем по обстоятельствам.
19 August 2005 20:33 Dimon aka Manowar

Не, про ридер ты не понял.

У меня есть метод
public IDataReader List() {
  SqlConnection myConn = ConnectionPool.Instance.GetConnection()
  SqlCommand myCmd = new SqlCommand("запрос", myConn);
  ...
  return myCmd.ExecuteReader(CommandBehaviour.CloseConnection);
}

Вернуть коннекцию в пул тут не представляется возможным, соотв. она зависает в active. Ну а метод я где-то там вызываю как IDataReader rdr = MyDDL.List(); и после использования делаю rdr.Close(). Т.е. тоже коннекция вроде никак не возвращается, но в списке active она остается, и к тому же будет уже закрыта. Вот и спрашивается - может раз в 10 вызовов пробегать по active и все закрытые коннекции грохать из него?
20 August 2005 23:11 Dimon aka Manowar

Кстати про открытые соединения...

Код из GetConnection()
if (closed.Count>0)
{
result=(SqlConnection) closed[0];
closed.Remove(result);
active.Add(result);
return result;
}
Вот здесь перед return result; надо бы сделать result.Open();, а то логика теряетсяУлыбка
22 August 2005 13:48 Nik Legaloff

да, верно

у меня то он есть, видимо я как то умудрился потерять 1 строку перенося сюда код и переформатируя его
 
Наш Киев

Apartments for Rent

Rambler's Top100
Рейтинг@Mail.ru
Идея: Dimon aka Manowar Программирование: Dimon aka Manowar Дизайн: Dan Lebedev
Хостинг от компании Parking.ru
Карта сайта