ADO.NET и Npgsql: соединения, команды, пул соединений
↑ 4.1 ADO.NET, Npgsql и Dapper · → Следующая
Зачем это на собесе
Вопросы про пул соединений и утечки соединений — классика. Они показывают, понимаете ли вы, что находится под ORM.
Объяснение
ADO.NET — базовый слой доступа к данным в .NET. Провайдер (Npgsql для PostgreSQL, Microsoft.Data.SqlClient для SQL Server) реализует общие абстракции.
| Абстракция | Роль |
|---|---|
DbConnection | соединение с БД |
DbCommand | SQL-команда с параметрами |
DbDataReader | потоковое чтение строк вперёд |
DbTransaction | транзакция |
DbDataSource (Npgsql NpgsqlDataSource) | фабрика соединений с пулом |
var dataSource = NpgsqlDataSource.Create(connectionString); // singleton на приложение
builder.Services.AddNpgsqlDataSource(connectionString);
await using var conn = await dataSource.OpenConnectionAsync(ct);
await using var cmd = new NpgsqlCommand("select id, name from users where id = $1", conn)
{
Parameters = { new() { Value = id } }
};
await using var reader = await cmd.ExecuteReaderAsync(ct);
while (await reader.ReadAsync(ct))
users.Add(new User(reader.GetGuid(0), reader.GetString(1)));Пул соединений
Открытие физического соединения дорого (TCP, TLS, аутентификация). Open() берёт соединение из пула, Dispose() (Close()) возвращает его обратно — физически оно не закрывается. Размер пула настраивается в строке подключения: Maximum Pool Size=100.
Нюансы и подводные камни
- Не закрыл соединение — оно не вернулось в пул: «timeout waiting for a connection from the pool». Всегда
using/await using. - Держите соединение как можно меньше: открыл — выполнил — закрыл.
- Максимальный размер пула × число инстансов не должен превышать
max_connectionsсервера PostgreSQL (используйте PgBouncer). ReadиExecuteReaderдержат соединение, пока reader открыт: не вызывайте второй запрос на том же соединении без MARS/multiplexing.- Для больших выборок используйте
CommandBehavior.SequentialAccessи потоковое чтение.
Практика
- Воспроизведите исчерпание пула, «забыв» закрыть соединение.
- Настройте
NpgsqlDataSourceв DI и прочитайте данные через reader. - Сравните время запроса с пулом и
Pooling=false.
Вопросы с ответами
Что происходит при conn.Close() при включённом пуле?
Соединение возвращается в пул и остаётся открытым на стороне БД, сбрасывается его состояние.
Почему возникает pool exhaustion?
Соединения не освобождаются (утечка), запросы слишком долгие, либо пул мал для нагрузки.
Зачем PgBouncer при пуле в приложении?
Ограничивает суммарное число соединений на сервере БД от многих инстансов приложения.