Connection Pooling y Tiempos de Espera de database/sql
*sql.DB es un pool de conexiones, no un único socket de larga duración.
Busca en todas las páginas de la documentación
*sql.DB es un pool de conexiones, no un único socket de larga duración.
Ajustar SetMaxOpenConns, la configuración de inactividad y los tiempos de vida evita que tu servicio agote la base de datos o bloquee goroutines esperando una conexión libre.
Combina los límites del pool con los plazos de context.Context para que las solicitudes HTTP abandonadas liberen las ranuras del pool de manera oportuna.
SetMaxOpenConns limita las conexiones simultáneas por proceso.
SetMaxIdleConns controla cuántas conexiones permanecen activas entre ráfagas.
SetConnMaxLifetime y SetConnMaxIdleTime rotan las conexiones para la higiene del balanceador de carga y las credenciales.
QueryContext y funciones similares respetan la cancelación mientras esperan una conexión o ejecutan una consulta.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
func ConfigurePool(db *sql.DB) {
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(30 * time.Minute)
db.SetConnMaxIdleTime(5 * time.Minute)
}
func GetOrder(ctx context.Context, db *sql.DB, id string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 300*time.Millisecond)
defer cancel()
var status string
err := db.QueryRowContext(ctx,
`SELECT status FROM orders WHERE id = $1`, id,
).Scan(&status)
return status, err
}Cuándo usar esto:
database/sql detrás de HTTP o gRPC.WaitCount aumenta o las consultas sobreviven a la desconexión del cliente.max_connections de la base de datos por el número de pods.package main
import (
"context"
"database/sql"
"fmt"
"time"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
defer db.Close()
db.SetMaxOpenConns(5)
db.SetMaxIdleConns(2)
db.SetConnMaxLifetime(time.Hour)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
var one int
err = db.QueryRowContext(ctx, `SELECT 1`).Scan(&one)
fmt.Println(one, err)
s := db.Stats()
fmt.Printf("waitCount=%d waitDuration=%s\n", s.WaitCount, s.WaitDuration)
}Lo que esto demuestra:
QueryRowContext respeta el plazo principal.db.Stats() expone WaitCount y WaitDuration para señales de saturación.sql.Open crea un gestor de pool; las conexiones se crean de forma perezosa hasta MaxOpenConns.QueryContext se bloquean hasta que una conexión se libera o ctx termina.MaxIdleConns; de lo contrario, se cierran.ConnMaxLifetime fuerza el reemplazo incluso si la conexión está en buen estado, lo que ayuda detrás de credenciales rotativas o PgBouncer.| Configuración | Efecto | Punto de partida |
|---|---|---|
SetMaxOpenConns | Límite estricto por proceso | floor(db_max / replicas) |
SetMaxIdleConns | Conexiones activas mantenidas | A menudo MaxOpenConns / 2 |
SetConnMaxLifetime | Edad máxima antes de reciclar | 15-60 minutos detrás del LB |
SetConnMaxIdleTime | Cierra inactividad después de la duración | 2-10 minutos |
PingContext | Comprobación de salud al inicio | Requerido en main |
ctx, cancel := context.WithTimeout(parent, 200*time.Millisecond)
defer cancel()
_, err := db.ExecContext(ctx, `SET statement_timeout = 150`)statement_timeout de Postgres protege cuando los drivers terminan el paquete actual lentamente.*sql.DB por DSN por proceso es lo típico; no abras un nuevo pool por solicitud.*sql.DB desde main a los repositorios; evita pools en init() sin pruebas.db.Stats() (InUse, Idle, WaitCount) en un temporizador en producción.MaxOpenConns ilimitado por defecto - Un pico de tráfico puede abrir miles de conexiones y alcanzar too many connections en Postgres. Solución: establece un límite explícito basado en la planificación de capacidad.MaxIdleConns enorme igual a MaxOpenConns en DBs pequeñas - Los sockets inactivos desperdician ranuras de DB. Solución: pool inactivo más pequeño que el límite abierto a menos que los datos de latencia digan lo contrario.Ping al inicio - Las credenciales incorrectas fallan en la primera solicitud del usuario. Solución: PingContext con un tiempo de espera de arranque en main.r.Context().WaitCount - La saturación parece consultas lentas. Solución: alerta sobre el crecimiento de la duración de espera antes de que los tiempos de consulta p99 se disparen.Close - CI agota los puertos efímeros. Solución: un pool por paquete de pruebas o un contenedor de pruebas compartido con limpieza.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
| PgBouncer / RDS Proxy | Muchos servicios pequeños comparten una DB | Necesitas persistencia de sentencias preparadas a nivel de sesión sin cuidado del modo de transacción |
Solo statement_timeout | La cancelación del driver es débil | Reemplazar completamente el contexto con ámbito de solicitud |
Conexión única (MaxOpenConns=1) | Bloqueos de archivos de SQLite, herramientas pequeñas | Manejadores HTTP concurrentes |
| Pool gestionado por ORM | El equipo ya está estandarizado en GORM | Necesitas db.Stats() explícito en los runbooks de operaciones |
sql.DB por esquema | Aislamiento estricto multi-inquilino | Un servicio con un usuario de rol es suficiente |
Divide max_connections de la base de datos por el número de réplicas, resta el margen para administración y migraciones, y luego limita por proceso.
Mide WaitCount y ajusta.
Evita nuevos usos después del plazo; el trabajo en curso termina a menos que el contexto lo cancele.
Combina con tiempos de vida por debajo de los tiempos de espera inactivos del balanceador de carga.
No siempre: las conexiones inactivas consumen ranuras de DB.
Empieza con la mitad del máximo abierto y ajusta con métricas.
Si no hay una conexión libre, QueryContext espera hasta que ctx termine y devuelve context.DeadlineExceeded.
Por eso el ctx del manejador es importante para la salud del pool.
No: los pools tienen ámbito de proceso.
Las solicitudes toman prestadas y devuelven conexiones rápidamente.
Establece MaxOpenConns(1), ejecuta dos consultas concurrentes, y verifica que la segunda respeta un tiempo de espera corto de ctx.
Usa httptest más errgroup en pruebas de integración.
Las bases de datos de archivos serializan las escrituras; aún así, establece límites razonables para el paralelismo de pruebas en memoria.
Las bases de datos SQL en red se benefician más del ajuste.
OpenConnections, InUse, Idle, WaitCount, WaitDuration, e histogramas de latencia de consulta etiquetados por nombre de sentencia.
Sí: inyecta el mismo *sql.DB en ambos servidores en main.
Pools separados solo para diferentes DSNs o requisitos de aislamiento.
El r.Context() del manejador debe ser el padre de cada QueryContext.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado Green Tea GC, go fix modernizers - verifica el parche en la compilación), chi (última versión - verifica en la compilación), gin (última versión - verifica en la compilación), echo (última versión - verifica en la compilación), google.golang.org/grpc (última versión - verifica en la compilación), sigs.k8s.io/controller-runtime (última versión - verifica en la compilación), kubebuilder (última versión - verifica en la compilación), tinygo (última versión - verifica los objetivos de la placa en la compilación), wazero (última versión - verifica en la compilación) y golangci-lint (última versión - verifica el conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 19 jul 2026