mysql connector net и пул соединений
Есть серверное приложение, использующее mysql connector net. В целях масштабируемости, в ходе чтения литературы, я узнал о существовании пулов соединений.
Гугл ответил на множество моих теоретических вопросов (например), но о практике молчит как партизан.
Меня начинают посещать смутные мысли, что пул соединений mysql connector net действует не явно. То есть при создании соединения мне достаточно указать в строке соединения Pooling=true, и на этом все. Я прав?
Если так, то у меня есть класс-интерфейс, который использует классы коннектора. Если у меня будет несколько экземпляров этого интерфейса, да при том в разных потоках, это будет один пул или нет?
И может стоит вообще отказаться от использования коннектора? Что в таком случае посоветуете использовать?
Сам проект(OpenSource, GNU\GPL), если кому будет интересно. Мой интерфейс - класс MySql, который я хочу заменить на DBLayer.
DBLayer на данный момент выглядит так:
{
private object[] _Config;
private string connectionString;
private IDbConnection conn;
public DBLayer(object[] _Config)
{
this._Config = _Config;
connectionString = string.Format("Server={0};Database={4};Port={1};User ID={2};Password={3};Pooling=true;Min Pool Size=0;Max Pool Size=100;Connection Lifetime=0",
((__Private)_Config[0]).mysqlwow_host,
((__Private)_Config[0]).mysqlwow_port,
((__Private)_Config[0]).mysqlwow_login,
((__Private)_Config[0]).mysqlwow_pass,
((__Private)_Config[0]).mysql_db_wwl);
conn = new MySqlConnection(connectionString);
if (Execute("/*!40101 SET NAMES utf8 */;") < 0)
throw new System.Exception("ERROR in DBLayer(object[] _Config)");
}
~DBLayer()
{
conn.Dispose();
}
public int Execute(string comm)
{
int result = -1;
conn.Open();
try
{
IDbCommand command = conn.CreateCommand();
command.CommandText = comm;
result = command.ExecuteNonQuery();
}
catch (Exception ee)
{
Logs.ELog(ee.ToString());
Logs.DBLog("Ошибка запроса " + comm);
result = -1;
}
conn.Close();
return result;
}
public IDataReader Query(string comm)
{
IDataReader result = null;
conn.Open();
try
{
IDbCommand command = conn.CreateCommand();
command.CommandText = comm;
IDataReader reader = command.ExecuteReader();
result = reader;
}
catch (Exception ee)
{
Logs.ELog(ee.ToString());
Logs.Log("Ошибка запроса " + comm);
result = null;
}
conn.Close();
return result;
}
}
Всем заранее спасибо.
Меня начинают посещать смутные мысли, что пул соединений mysql connector net действует не явно. То есть при создании соединения мне достаточно указать в строке соединения Pooling=true, и на этом все. Я прав?
Я не специалист в работе с MySql Connector.NET, но все же посмотрите здесь.
Да, настройка Pooling управляет существованием внутреннего пула соединений у провайдеров данных в .NET. Помимо нее существует еще две настройки: "Min Pool Size" и "Max Pool Size", которые описывают размер пула (по-умолчанию 0 и 100). В пуле находятся внутренние, физические подключения к СУБД, а не экземпляры DbConnection или MySqlConnection, но, в то же время, каждый их экземпляр однозначно связан со внутренним соединением. Если пулинг включен, то "закрытие" (вызов Close или Dispose) MySqlConnection приводит к "отвязыванию" физического подключения; в зависимости от настроек пула (min и max) это физическое соединение будет доступно для создания нового MySqlConnection или же будет автоматически закрыто провайдером через некоторое время.
Обращение к пулу соединений происходит при создании экземпляра соединения (MySqlConnection), пул выбирается на основе самой строки подключения: если вы хотите, чтобы использовался один и тот же пул соединений, нужно передавать в конструктор идентичные строки подключения. При создании первого подключения пул будет создан автоматически (если, он нужен, конечно) - я предлагаю вам проверить это, создав соединение с ненулевым Min Pool Size: в выводе команды netstat -an должны появиться TCP/IP соединения с СУБД.
Да, настройка Pooling управляет существованием внутреннего пула соединений у провайдеров данных в .NET. Помимо нее существует еще две настройки: "Min Pool Size" и "Max Pool Size", которые описывают размер пула (по-умолчанию 0 и 100). В пуле находятся внутренние, физические подключения к СУБД, а не экземпляры DbConnection или MySqlConnection, но, в то же время, каждый их экземпляр однозначно связан со внутренним соединением. Если пулинг включен, то "закрытие" (вызов Close или Dispose) MySqlConnection приводит к "отвязыванию" физического подключения; в зависимости от настроек пула (min и max) это физическое соединение будет доступно для создания нового MySqlConnection или же будет автоматически закрыто провайдером через некоторое время.
Обращение к пулу соединений происходит при создании экземпляра соединения (MySqlConnection), пул выбирается на основе самой строки подключения: если вы хотите, чтобы использовался один и тот же пул соединений, нужно передавать в конструктор идентичные строки подключения.
Огромное спасибо. По ссылке я уже был раньше, тем более что это оффициальная документация коннектора. В английском не силен, смог только понять некоторые опции строки подключения. В первом посту я использовал Pooling, Min Pool Size, Max Pool Size и Connection Lifetime. Значит, я все делаю правильно.
Еще один вопрос: достаточно ли это экономично, создавать экземпляры моего интерфейса, а значит и экземпляры MySqlConnection для каждого клиента? (мое приложение - серверная часть, работающая с клиентами, клиентов может быть много, порядка 1000, обработка клиентов асинхронная)
В целом да, правда вашему классу DBLayer необходима реализация интерфейса IDisposable (по ссылке есть пример реализации) для корректного освобождения экземпляра подключения.
UPD. И, кстати, странный способ передавать параметры через массив object'ов. NameValueCollection (или IDictionary) все же человечнее.
UPD. И, кстати, странный способ передавать параметры через массив object'ов. NameValueCollection (или IDictionary) все же человечнее.
Благодарю. Я на этом проекте изучал C#, и до таких вещей, как NameValueCollection (или IDictionary) еще не дошел :rolleyes:
Был бы благодарен за ссылочки по поводу этих классов. Еще я читал про некие синглтоны, думал позже с ними разобраться.
И еще, по ссылке реализацию IDisposable понял, я правильно сделал?
{
private bool disposed = false;
private object[] _Config;
private string connectionString;
private IDbConnection conn;
public DBLayer(object[] _Config)
{
this._Config = _Config;
connectionString = string.Format("Server={0};Database={4};Port={1};User ID={2};Password={3};Pooling=true;Min Pool Size=3;Max Pool Size=100;Connection Lifetime=0",
((__Private)_Config[0]).mysqlwow_host,
((__Private)_Config[0]).mysqlwow_port,
((__Private)_Config[0]).mysqlwow_login,
((__Private)_Config[0]).mysqlwow_pass,
((__Private)_Config[0]).mysql_db_wwl);
conn = new MySqlConnection(connectionString);
Execute("/*!40101 SET NAMES utf8 */;");
}
~DBLayer()
{
Dispose(false);
}
public int Execute(string comm)
{
int result = -1;
conn.Open();
try
{
IDbCommand command = conn.CreateCommand();
command.CommandText = comm;
result = command.ExecuteNonQuery();
}
catch (Exception ee)
{
Logs.ELog(ee.ToString());
Logs.DBLog("Ошибка запроса " + comm);
result = -1;
}
conn.Close();
return result;
}
public IDataReader Query(string comm)
{
IDataReader result = null;
conn.Open();
try
{
IDbCommand command = conn.CreateCommand();
command.CommandText = comm;
IDataReader reader = command.ExecuteReader();
result = reader;
}
catch (Exception ee)
{
Logs.ELog(ee.ToString());
Logs.Log("Ошибка запроса " + comm);
result = null;
}
conn.Close();
return result;
}
public string[] QueryOneRow(string comm)
{
string[] result;
conn.Open();
try
{
IDbCommand command = conn.CreateCommand();
command.CommandText = comm;
IDataReader reader = command.ExecuteReader();
result = new string[reader.FieldCount];
while (reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
result = reader.ToString();
}
}
}
catch (Exception ee)
{
Logs.ELog(ee.ToString());
Logs.Log("Ошибка запроса " + comm);
result = null;
}
conn.Close();
return result;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
conn.Dispose();
}
}
disposed = true;
}
}
А то непонятно, что такое handle, и зачем вообще импортировать функции из внешних библиотек.
Был бы благодарен за ссылочки по поводу этих классов.
Мне пересказать MSDN? ;)
Класс System.Collections.Specialized.NameValueCollection - набор строковых пар имя-значение (ключ - строка, значение - строка).
Интерфейс System.Collections.IDictionary - более общая абстракция, набор пар объект-объект, этот интерфейс реализует, например, класс Hashtable.
Правильно.
В примере показана работа с unmanaged функциями Win32 API, где handle - некий ресурс, например, дескриптор файла.
Класс System.Collections.Specialized.NameValueCollection - набор строковых пар имя-значение (ключ - строка, значение - строка).
Нет, спасибо, msdn пересказывать не надо, он очень большой ;) :D
Еще один вопрос, если вы конечно знакомы с синглтонами, предпочтительнее разбираться с ними, или с NameValueCollection и IDictionary? И передавать в свои дочерние потоки лучше objectы на IDictionary или сами экземпляры?
Большое вам спасибо, вы мне очень помогли :)
UPD: И еще один вопрос :D
Клиенты у меня храняться в List<ConnectionInfo>, где в ConnectionInfo хранится класс сокета, буффер, и класс, который обрабатывает запросы клиента. Внутри последнего класса и хранится мой DBLayer. Вопрос: нужно ли мне явным образом вызывать деструктор DBLayer при удаление обьекта ConnectionInfo из List?
Не вижу вопоса. Синглтон - это паттерн проектирования такой (единственный экземпляр класса в памяти).
Передавайте то, с чем удобнее работать.
Да вызывать Dispose нужно (только в .NET нет понятия "деструктор"), но вообще, в противном случае, он будет вызван сборщиком мусора через неопределенный промежуток времени. Вообще, я подозреваю, что ConnectionInfo должен не только DBLayer освобождать....
Передавайте то, с чем удобнее работать.
Удобнее с экземплярами, но ведь ссылка на обьект занимает меньше памяти чем сам обьект? С другой стороны, обеспечение потокобезопасности при работе с ссылками на обьект тоже ресурсоемко. Как экономичнее?
За остальное спасибо. Думаю идеально будет использовать IDictionary одним экземпляром.
Если объект не модифицируется, то количество потоков, которые читают из него не важно и защита не нужна.
А почему бы собственно не передавать готовую строку подключения?
И еще. Не ищите проблем спроизводительностью, пока не получили хотябы первый прототип программы. Уверяю, в конечном счете наибольшую просадку вы обнаружите совсем не в том месте, где ожидаете сейчас. Не занимайтесь преждевременной оптимизацией. ;)
И еще. Не ищите проблем спроизводительностью, пока не получили хотябы первый прототип программы. Уверяю, в конечном счете наибольшую просадку вы обнаружите совсем не в том месте, где ожидаете сейчас. Не занимайтесь преждевременной оптимизацией. ;)
Дык программе-то уже вообщем-то более полугода. Правда начинал я ее писать давно, а недавно, восполнив значительный пробел в знаниях, сделал из программульки OpenSource проект, и продолжил разработку. Видели бы вы код в то славное время... вы бы месяц по полу от смеха катались :D
Но самое удивительное, это то, что все работает. И сейчас, перед вводом новой функциональности, я решил заняться приведением кода в надлежащий вид. Ну и теперь уже хочется строить все сразу правильно. Потому и пекусь о каждой мелочи.
По поводу строки подключения, _Config - это экземпляр класса Config, который содержит внутри себя множество параметров. При старте сервер читает их из xml файла. Так что цельной строкой не обойдешься. Хотя, если конкретно с DBLayer строку можно сформировать заранее, то в других классах ничего такого не получится. Все равно встает проблема передачи _Config между потоками.