Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Драйвер go-mssqldb использует встроенный пул соединений, предоставляемый пакетом database/sql языка Go. Каждый экземпляр sql.DB поддерживает пул неактивных соединений, которые автоматически переиспользуются. В этой статье объясняется, как настроить пул под вашу нагрузку.
Как работает бассейн
Когда вы вызываете db.QueryContext, db.ExecContext, или любой другой метод базы данных:
- Пул пытается найти свободное соединение.
- Если свободных соединений нет и пул ещё не достиг максимального размера, создаётся новое соединение.
- Если пул на максимальной мощности, вызов блокируется до тех пор, пока не станет доступно соединение.
- После завершения операции соединение возвращается в пул.
Методы конфигурации пула
Настройте пул с помощью методов объекта *sql.DB:
| Метод | Description |
|---|---|
db.SetMaxOpenConns(n) |
Максимальное количество открытых соединений (используемых + неактивных). По умолчанию: 0 (без ограничений). |
db.SetMaxIdleConns(n) |
Максимальное количество неактивных соединений в пуле. По умолчанию: 2. |
db.SetConnMaxLifetime(d) |
Максимальное общее время повторного использования соединения. По умолчанию: 0 (без ограничения). |
db.SetConnMaxIdleTime(d) |
Максимальное время, в течение которого соединение может простоять перед закрытием. По умолчанию: 0 (без ограничения). |
Пример
Настройте пул сразу после открытия базы данных:
db, err := sql.Open("sqlserver", connString)
if err != nil {
log.Fatal(err)
}
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(1 * time.Minute)
Воспринимайте эти значения как отправную точку, а не как универсальные стандарты. Для многих сервисов первым полезным шагом будет задать ограниченные значения для MaxOpenConns и MaxIdleConns, а ограничения по времени жизни и простою добавлять только тогда, когда сценарий развертывания может приводить к устаревшим или неравномерно распределённым соединениям.
Рекомендуемые параметры
| Сценарий | MaxOpen | MaxIdle | MaxLifetime | MaxIdleTime |
|---|---|---|---|---|
| Веб-приложение на стабильном сетевом пути SQL Server | 25 | 10 | 0 | 0 |
| Веб-приложение через Azure SQL, шлюз или балансировщик нагрузки | 25 | 10 | 5 мин | 1 минута |
| Сервис с высокой пропускной способностью | 50-100 | 25 | 5 мин | 30 секунд |
| Фоновая работа / инструмент CLI | 5 | 2 | 0 | 0 |
| База данных SQL Azure (Basic/Standard) | 10-20 | 5 | 5 мин | 1 минута |
Tip
Установите значение MaxOpenConns ниже предела количества подключений для вашего экземпляра SQL Server или уровня службы Azure SQL. Превышение максимального количества одновременных соединений сервера приводит к сбоям входа для всех клиентов.
Короткие ConnMaxLifetime и ConnMaxIdleTime значения снижают вероятность застоявшихся соединений после отказа или повторного использования шлюза, но также увеличивают отток соединений. Если ваше приложение напрямую подключается к стабильному экземпляру SQL Server и вы не видите сбоев застоявшегося соединения, оставлять оба значения на уровне 0 — разумно.
Отслеживание статистики пула
Используйте db.Stats() для чтения текущей статистики пула:
stats := db.Stats()
fmt.Printf("Open: %d, InUse: %d, Idle: %d\n",
stats.OpenConnections, stats.InUse, stats.Idle)
fmt.Printf("WaitCount: %d, WaitDuration: %v\n",
stats.WaitCount, stats.WaitDuration)
Ключевые поля:
| Поле | Description |
|---|---|
OpenConnections |
Общее количество открытых соединений (в использовании + в простое). |
InUse |
Соединения сейчас проверены звонящими. |
Idle |
Соединения ждут в бассейне. |
WaitCount |
Общее количество раз, когда звонящий должен был ждать соединения. |
WaitDuration |
Общее накопленное время ожидания. |
Если WaitCount растёт стабильно, не увеличивайте MaxOpenConns автоматически. Сначала убедитесь, что строки, транзакции и выделенные соединения быстро закрываются, и убедитесь, что сервер поддерживает более крупный пул.
SessionInitSQL
Используйте SessionInitSQL, чтобы выполнять SQL-запрос для каждого нового соединения при его поступлении в пул соединений. Эта функция полезна для настройки параметров на уровне сессии:
import (
"database/sql"
"github.com/microsoft/go-mssqldb"
"github.com/microsoft/go-mssqldb/msdsn"
)
config := msdsn.Config{
Host: "<server>",
Port: 1433,
Database: "AdventureWorks2025",
}
connector := mssql.NewConnectorConfig(config)
connector.SessionInitSQL = "SET ANSI_NULLS ON; SET QUOTED_IDENTIFIER ON"
db := sql.OpenDB(connector)
Закрепление соединения
Некоторые операции закрепляют соединение, чтобы оно не возвращалось в пул до завершения операции:
-
Транзакции (
db.BeginTx) — соединение закрепляется доCommit()вызова илиRollback()вызова. -
Одиночные соединения (
db.Conn) — соединение остаётся закреплённым до вызоваconn.Close(). -
Открытые строки (
db.QueryContext) — соединение закрепляется доrows.Close()вызова.
Всегда закрывайте эти ресурсы как можно скорее, чтобы не истощить пул.
Обнаружить и устранить истощение бассейна
Исчерпание пула происходит, когда все соединения используются и пул достиг предела MaxOpenConns. Новые звонящие блокируют до восстановления соединения. Симптомы включают высокую задержку, накопление горутинов и возможные ошибки в контекстном дедлайне.
Монитор усталости
Периодически проводите опросы db.Stats() и оповещайте при обнаружении спора:
func monitorPool(ctx context.Context, db *sql.DB, interval time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
var lastWaitCount int64
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
stats := db.Stats()
newWaits := stats.WaitCount - lastWaitCount
lastWaitCount = stats.WaitCount
if newWaits > 0 {
log.Printf("POOL CONTENTION: %d new waits, avg wait %v, open=%d, inUse=%d, idle=%d",
newWaits, stats.WaitDuration/time.Duration(stats.WaitCount),
stats.OpenConnections, stats.InUse, stats.Idle)
}
}
}
}
Распространенные проблемы и их решения
| Причина | Симптом | Solution |
|---|---|---|
MaxOpenConns слишком низкая для нагрузки |
WaitCount устойчиво растёт. |
Увеличьте MaxOpenConns. |
| Строки, не замкнутые в путях ошибок |
InUse Растёт, Idle остаётся на 0. |
Используйте defer rows.Close() сразу после QueryContext. |
| Длительные транзакции |
InUse Держится высоко. |
Держите транзакции короткими. Используйте контекстные тайм-ауты. |
db.Conn использовался без нужды |
InUse выше, чем ожидалось. |
Используйте db.Conn только тогда, когда вам нужно состояние в рамках сеанса (временные таблицы). |
MaxOpenConns не задано (без ограничений) |
Сотни открытых соединений под нагрузкой. | Всегда задавайте для MaxOpenConns ограниченное значение. |
Проверки здоровья и обнаружение устаревших соединений
Пул не выполняет активную проверку неактивных соединений. Соединение, которое простояло, пока сервер его перерабатывал, выходит из строя при следующем использовании. Настройте ConnMaxLifetime и ConnMaxIdleTime так, чтобы они выполняли ротацию соединений до того, как те устареют:
// Rotate connections every 5 minutes to stay compatible
// with load balancers and Azure SQL failover.
db.SetConnMaxLifetime(5 * time.Minute)
// Close connections that have been idle for over 1 minute
// to reduce the number of stale connections.
db.SetConnMaxIdleTime(1 * time.Minute)
Замечание
Когда ConnMaxIdleTime заблаговременно закрывает неактивные соединения, в журналах SQL Server может отображаться разрыв соединений. Это ожидаемое поведение, а не утечка соединения. Если ваш DBA сообщает о неожиданных закрытиях соединения, убедитесь, что ConnMaxIdleTime настройка соответствует ожиданиям команды по мониторингу.
Если ваше приложение подключается через балансировщик нагрузки или Azure SQL с георепликацией, установите ConnMaxLifetime на 5 минут или меньше. Эта настройка обеспечивает перераспределение соединений между репликами после отказа.
Проверка подключения при запуске
Всегда вызывайте db.PingContext после открытия базы данных, чтобы убедиться, что строка подключения указана правильно и сервер доступен:
db, err := sql.Open("sqlserver", connString)
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
log.Fatalf("Cannot connect to database: %v", err)
}
Метрики экспортного пула
Предоставьте статистику пула своей системе мониторинга, периодически считывая db.Stats():
Пример Прометея
Зарегистрируйте метрики, которые отслеживают статистику пула и периодически обновляются:
import "github.com/prometheus/client_golang/prometheus"
var (
dbOpenConns = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "db_open_connections",
Help: "Number of open database connections.",
})
dbInUseConns = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "db_in_use_connections",
Help: "Number of connections currently in use.",
})
dbWaitCount = prometheus.NewCounter(prometheus.CounterOpts{
Name: "db_wait_count_total",
Help: "Total number of times a caller waited for a connection.",
})
dbWaitDuration = prometheus.NewCounter(prometheus.CounterOpts{
Name: "db_wait_duration_seconds_total",
Help: "Total wait time for a connection.",
})
)
func init() {
prometheus.MustRegister(dbOpenConns, dbInUseConns, dbWaitCount, dbWaitDuration)
}
func recordPoolMetrics(ctx context.Context, db *sql.DB) {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
var lastWaitCount int64
var lastWaitDuration time.Duration
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
stats := db.Stats()
dbOpenConns.Set(float64(stats.OpenConnections))
dbInUseConns.Set(float64(stats.InUse))
dbWaitCount.Add(float64(stats.WaitCount - lastWaitCount))
dbWaitDuration.Add((stats.WaitDuration - lastWaitDuration).Seconds())
lastWaitCount = stats.WaitCount
lastWaitDuration = stats.WaitDuration
}
}
}
Контрольный список конфигурации пула
| Area | Recommendation |
|---|---|
MaxOpenConns |
Всегда устанавливайте ограниченное значение. Сопоставьте это с уровнем параллелизма вашей рабочей нагрузки, не превышая лимит подключений сервера. |
MaxIdleConns |
Установите не менее половины от MaxOpenConns. Слишком малое количество неактивных соединений приводит к частым издержкам на повторное подключение. |
ConnMaxLifetime |
Установите значение 5 минут для Azure SQL или сред с балансировкой нагрузки. Это предотвращает накопление застоявшихся соединений. |
ConnMaxIdleTime |
Установите на 30-60 секунд, чтобы закрыть соединения, которые больше не нужны. |
| Контроль | Отслеживайте db.Stats() и настраивайте оповещения о росте WaitCount. |
| Очистка ресурсов | Всегда defer rows.Close(), defer tx.Rollback(), и defer conn.Close(). |
| Валидация запуска | Позвони db.PingContext после sql.Open , чтобы подтвердить связь. |