Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Las aplicaciones Production Go necesitan un manejo estructurado de errores para distinguir entre fallos transitorios que puedes repetir y errores permanentes que requieren intervención humana. Este artículo trata sobre la clasificación de errores, patrones de reintentos y estrategias de resiliencia para el go-mssqldb conductor.
Estructura de error de SQL Server
Cuando SQL Server devuelve un error, el go-mssqldb controlador lo envuelve en una mssql.Error estructura. Utiliza una asertura de tipo para acceder a los campos de error estructurados:
import (
"database/sql"
"errors"
"fmt"
mssql "github.com/microsoft/go-mssqldb"
)
func handleError(err error) {
var mssqlErr mssql.Error
if errors.As(err, &mssqlErr) {
fmt.Printf("Number: %d\n", mssqlErr.Number)
fmt.Printf("State: %d\n", mssqlErr.State)
fmt.Printf("Class: %d\n", mssqlErr.Class)
fmt.Printf("Message: %s\n", mssqlErr.Message)
fmt.Printf("Server: %s\n", mssqlErr.ServerName)
fmt.Printf("Proc: %s\n", mssqlErr.ProcName)
fmt.Printf("Line: %d\n", mssqlErr.LineNo)
}
}
Campos de error
| Campo | Tipo | Description |
|---|---|---|
Number |
int32 |
Número de error de SQL Server. Asigna a sys.messages. |
State |
uint8 |
Estado de error. Proporciona contexto adicional para el mismo número de error. |
Class |
uint8 |
Nivel de gravedad (0-25). Las gravidades 11-16 son corregibles por el usuario. La gravedad 17+ indica problemas de recursos o de sistema. |
Message |
string |
Texto de error del servidor comprensible para el usuario. |
ServerName |
string |
Nombre de la instancia de SQL Server que generó el error. |
ProcName |
string |
Procedimiento almacenado o nombre de función donde ocurrió el error. Vacío para consultas ad hoc. |
LineNo |
int32 |
Número de línea en el lote Transact-SQL (T-SQL) o procedimiento almacenado. |
Niveles de gravedad
| Intervalo de gravedad | Meaning | Action |
|---|---|---|
| 0-10 | Mensajes informativos | Sin error. Regístralo si resulta útil. |
| 11-16 | Errores corregibles por el usuario | Arregla la consulta, los parámetros o los permisos. |
| 17-19 | Errores de recursos | vuelva a intentarlo. El servidor puede estar bajo carga o sin recursos. |
| 20-25 | Errores fatales | La conexión está interrumpida. Reconecta y vuelve a intentarlo. |
Clasificar los errores como transitorios o permanentes
Los errores transitorios son condiciones temporales que se resuelven por sí solas, como interrupciones de red, limitación de conexión o breves contenciones de recursos. Los errores permanentes requieren cambios en el código o en la configuración.
Números de error transitorios comunes
Utilice el siguiente catálogo compartido como lista canónica de errores transitorios en el establecimiento de conexiones y en el transporte de rutas de solicitud:
Los siguientes errores pueden ser transitorios cuando ocurren durante el establecimiento de la conexión o al enviar una solicitud al servidor. Reintentar con un retroceso breve y limitado. Los errores que persisten después de unos pocos intentos suelen indicar un problema de configuración, como un servidor equivocado, permisos faltos, ajustes de cifrado incompatibles o cuota agotada, que intentar de nuevo no solucionará.
| Error | Tipo de error | Message | Troubleshooting |
|---|---|---|---|
64 |
Transporte durante el inicio de sesión | A connection was successfully established with the server, but then an error occurred during the login process. (provider: TCP Provider, error: 0 - The specified network name is no longer available.) |
La conexión TCP se interrumpe en medio del protocolo de enlace. No es un fallo de credenciales. Si persiste, compruebe si hay inestabilidad en la red del cliente o algún dispositivo intermedio que interrumpa conexiones establecidas solo parcialmente. |
233 |
Transporte previo al inicio de sesión o TLS | The client was unable to establish a connection because of an error during connection initialization process before login. |
El servidor suele devolver este error cuando no puede aceptar la conexión debido al agotamiento de recursos, un límite de conexión o un cliente no soportado. No es un fallo de credenciales. Compruebe el estado del servidor y, a continuación, compruebe el tiempo de espera de inicio de sesión del cliente, la configuración de TLS y la compatibilidad de la versión de TLS de cliente/servidor. |
4060 |
Disponibilidad o acceso a bases de datos | Cannot open database "%.*ls" requested by the login. The login failed. |
El inicio de sesión se autentica, pero no puede abrir la base de datos solicitada. Las causas transitorias incluyen que la base de datos está en transición (conmutación por error, restauración, escalado) o pausada automáticamente. Las causas persistentes (la base de datos no existe, el inicio de sesión carece de acceso) no se solucionarán con un nuevo intento; comprueba el nombre de la base de datos, la asignación de inicio de sesión y el estado de la base de datos. |
4221 |
Transición secundaria legible | Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. |
La réplica no está disponible para el inicio de sesión porque faltan versiones de fila para las transacciones que estaban en curso cuando se recicló la réplica. Revierte o confirma las transacciones activas en el servidor principal para resolver el problema. Mitíguelo evitando transacciones de escritura prolongadas en la base de datos principal. |
10053 |
Aborto del transporte local | A transport-level error has occurred when sending the request to the server. (provider: TCP Provider, error: 0 - An established connection was aborted by the software in your host machine.) |
El extremo local interrumpe la conexión. Compruebe el estado de la red del lado cliente y cualquier firewall local o cliente VPN. |
10054 |
Restablecimiento del transporte remoto | A transport-level error has occurred when sending the request to the server. (provider: TCP Provider, error: 0 - An existing connection was forcibly closed by the remote host.) |
El extremo remoto envía un reinicio de TCP. Causas comunes: el proceso remoto se bloqueó, un firewall forzó un restablecimiento de la conexión o la puerta de enlace de Azure SQL cerró una conexión inactiva. Para los patrones de reinicio por inactividad, habilite TCP keepalive en el cliente o reduzca el tiempo de espera de inactividad del pool de conexiones. |
10060 |
Tiempo de espera de conexión | A connection attempt failed because the connected party did not properly respond after a period of time. |
El servidor o un dispositivo de red intermedio no respondió antes del tiempo de espera de la conexión TCP. Comprueba el estado del servidor, el enrutamiento, las reglas del firewall y si el host y el puerto configurados son accesibles. |
10928 |
Límite de recursos en bases de datos | Resource ID: %d. The %s limit for the database is %d and has been reached. |
La base de datos supera un límite de gobernanza de recursos Azure SQL. El identificador de recurso 1 indica el límite de trabajo; El identificador de recurso 2 indica el límite de sesión. Identifique el tipo de límite a partir del mensaje y reduzca la concurrencia, amplíe la base de datos o acorte las operaciones de larga duración que mantienen el recurso. |
10929 |
Limitación del rendimiento de la base de datos | Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d, and the current usage for the database is %d. However, the server is currently too busy to support requests greater than %d for this database. |
La base de datos ha superado su garantía mínima y el servidor subyacente está limitando el rendimiento. El reintento suele tener éxito cuando disminuye la carga del vecino. Las apariciones sostenidas indican que necesita un nivel de servicio superior o un entorno menos ruidoso. |
40020, 40143, , 40166, 40540 |
Subcódigo de conmutación por error de Azure SQL | Notificado en el Error code %d campo del error 40197 durante la conmutación por error. |
Subcódigos incrustados en un mensaje de conmutación por error 40197 que, en algunas rutas, aparecen como número de error de primer nivel. Tratarlos igual que 40197. |
40197 |
Conmutación por error de Azure SQL | The service has encountered an error processing your request. Please try again. Error code %d. |
Una actualización de software, un error de hardware u otro evento de conmutación por error en Azure SQL. Al volver a conectarse, se le redirige a una réplica en buen estado. El código de error incluido identifica el tipo de conmutación por error. Si el error persiste, capture el identificador de seguimiento de sesión y póngase en contacto con el soporte técnico. |
40501 |
Limitación de Azure SQL | The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. |
Limitación del motor de Azure SQL. El tiempo de espera mínimo recomendado es de 10 segundos. La limitación sostenida indica que la carga de trabajo ha superado la asignación de recursos de la base de datos; amplíe el nivel de servicio o reduzca la concurrencia. |
40613 |
Base de datos no disponible | Database '%.*ls' on server '%.*ls' is not currently available. Please retry the connection later. If the problem persists, contact customer support, and provide them with the session tracing ID of '%.*ls'. |
La base de datos no está disponible, normalmente en medio de una conmutación por error o brevemente durante una operación de escalado. Vuelve a intentarlo con un retardo; si el problema persiste más allá de unos minutos, anota el ID de seguimiento de la sesión y abre un caso de soporte técnico. |
42108 |
Grupo de SQL en pausa | Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. |
El grupo de SQL dedicado (Synapse) está en un estado en pausa. El reintento solo tendrá éxito una vez que se haya reanudado el grupo. Reanuda el grupo de forma explícita o programa la carga de trabajo para que se ejecute una vez que el grupo se haya reanudado. |
42109 |
Reanudación del grupo de SQL | The SQL pool is warming up. Please try again. |
El grupo de SQL dedicado se está reactivando. Vuelve a intentarlo con un retardo hasta que el grupo esté en línea; el tiempo de calentamiento suele durar unos minutos. |
49918 |
Escasez de recursos de servicio | Cannot process request. Not enough resources to process request. The service is currently busy. Please retry the request later. |
El servidor no puede asignar recursos suficientes actualmente para satisfacer la solicitud. Vuelve a intentarlo con un retardo. Si el error persiste, aumente la capacidad de la base de datos o del grupo elástico. |
49919 |
Limitación de operaciones de administración | Cannot process create or update request. Too many create or update operations in progress for subscription "%ld". |
Límite de concurrencia a nivel de suscripción en las operaciones de administración. Reduzca las llamadas de creación y actualización paralelas o escalonelas. |
49920 |
Limitación de velocidad de las operaciones de suscripción | Cannot process request. Too many operations in progress for subscription "%ld". |
Límite de concurrencia en el nivel de suscripción en operaciones en curso. Reduce el paralelismo o espera a que finalicen las operaciones en curso. |
Los errores en el nivel de instrucción no figuran en esta lista porque se producen después de que se haya establecido la conexión y el error permite seguir utilizando la sesión. Los errores de instrucción más comunes que se pueden reintentar son el 1205 (víctima de interbloqueo) y el 1222 (tiempo de espera de solicitud de bloqueo agotado). Reintenta toda la transacción en lugar de la única instrucción que ha generado un error.
El texto del mensaje de error procede de Azure SQL errores de conexión transitorios. Estos errores se pueden volver a intentar en SQL Server, Azure SQL Database, Azure SQL Managed Instance, la base de datos SQL de Microsoft Fabric y los pools SQL dedicados de Azure Synapse Analytics.
La siguiente isTransient función muestra un patrón de implementación Go para la clasificación de reintentos. Trata el catálogo compartido anterior como la fuente de verdad y mantén tu búsqueda de código alineada con él.
// isTransient returns true if the error is a transient SQL Server error
// that is likely to succeed on retry.
func isTransient(err error) bool {
var mssqlErr mssql.Error
if !errors.As(err, &mssqlErr) {
// Network errors, context deadlines, and connection resets
// are also transient.
return isNetworkError(err)
}
if isTransientSQLNumber(mssqlErr.Number) {
return true
}
// Severity 17-19 indicates resource issues that are typically transient.
return mssqlErr.Class >= 17 && mssqlErr.Class <= 19
}
// Keep this lookup synchronized with the shared transient catalog above.
var transientSQLNumbers = map[int32]struct{}{
64: {}, // Transport/connection error.
1205: {}, // Deadlock victim.
40197: {}, // Service error processing request.
40501: {}, // Service is currently busy.
40613: {}, // Database is currently unavailable.
49918: {}, // Cannot process request: not enough resources.
49919: {}, // Cannot process create/update request.
49920: {}, // Cannot process request: too many operations.
}
func isTransientSQLNumber(number int32) bool {
_, ok := transientSQLNumbers[number]
return ok
}
Si experimentas errores de configuración y cuota, corrige la capacidad subyacente, la base de datos o la configuración de red antes de volver a intentarlo. Algunos ejemplos son:
-
40544(cuota de tamaño de base de datos) -
4060(no puede abrir la base de datos) -
40615(regla del firewall)
Detectar errores de red
Los errores a nivel de red no generan valores mssql.Error. Comprueba los tipos de error de red Go más comunes:
import (
"context"
"errors"
"net"
"io"
)
func isNetworkError(err error) bool {
if err == nil {
return false
}
// Context deadline exceeded or canceled
if errors.Is(err, context.DeadlineExceeded) {
return true
}
// Connection reset or broken pipe
var netErr *net.OpError
if errors.As(err, &netErr) {
return true
}
// Unexpected EOF (server dropped the connection)
if errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) {
return true
}
return false
}
Implementa el reintento con retroceso exponencial
Reintenta los errores transitorios con retrasos crecientes entre intentos. Este enfoque da tiempo al servidor para recuperarse y evita saturarlo con repeticiones rápidas.
import (
"context"
"database/sql"
"errors"
"fmt"
"log"
"math"
"math/rand"
"time"
mssql "github.com/microsoft/go-mssqldb"
)
// RetryConfig controls retry behavior.
type RetryConfig struct {
MaxAttempts int // Maximum number of attempts (including the first).
BaseDelay time.Duration // Initial delay before the first retry.
MaxDelay time.Duration // Upper bound on delay between retries.
}
// DefaultRetryConfig provides sensible defaults for SQL Server workloads.
var DefaultRetryConfig = RetryConfig{
MaxAttempts: 5,
BaseDelay: 100 * time.Millisecond,
MaxDelay: 10 * time.Second,
}
// RetryFunc executes fn with retries for transient errors.
func RetryFunc(ctx context.Context, cfg RetryConfig, fn func(ctx context.Context) error) error {
var lastErr error
for attempt := 0; attempt < cfg.MaxAttempts; attempt++ {
lastErr = fn(ctx)
if lastErr == nil {
return nil
}
if !isTransient(lastErr) {
return lastErr // Permanent error, don't retry.
}
if attempt == cfg.MaxAttempts-1 {
break // Last attempt, don't sleep.
}
delay := calculateDelay(attempt, cfg.BaseDelay, cfg.MaxDelay)
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(delay):
}
}
return lastErr
}
func calculateDelay(attempt int, baseDelay, maxDelay time.Duration) time.Duration {
// Exponential backoff: base * 2^attempt
delay := time.Duration(float64(baseDelay) * math.Pow(2, float64(attempt)))
if delay > maxDelay {
delay = maxDelay
}
// Add jitter: +/- 25% to avoid thundering herd
jitter := time.Duration(rand.Int63n(int64(delay) / 2))
return delay/2 + jitter
}
// isTransient classifies retryable SQL Server errors.
// For a fuller example, see "Classify errors as transient or permanent" earlier in this article.
func isTransient(err error) bool {
var mssqlErr mssql.Error
if !errors.As(err, &mssqlErr) {
return errors.Is(err, context.DeadlineExceeded)
}
switch mssqlErr.Number {
case 1205, 40197, 40501, 40613, 49918, 49919, 49920:
return true
}
return mssqlErr.Class >= 17 && mssqlErr.Class <= 19
}
// getEmployeeCount wraps a query with automatic retry.
func getEmployeeCount(ctx context.Context, db *sql.DB) (int, error) {
var count int
err := RetryFunc(ctx, DefaultRetryConfig, func(ctx context.Context) error {
// This query executes on every retry attempt until success or exhaustion.
return db.QueryRowContext(ctx, "SELECT COUNT(*) FROM HumanResources.Employee").Scan(&count)
})
return count, err
}
// Example call site (assumes db is already initialized).
func example(ctx context.Context, db *sql.DB) {
queryCtx, cancel := context.WithTimeout(ctx, 15*time.Second)
defer cancel()
count, err := getEmployeeCount(queryCtx, db)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Employee count: %d\n", count)
}
Controlar interbloqueos
Los interbloqueos (error 1205) son el error transitorio más habitual en las aplicaciones multiusuario. SQL Server termina automáticamente una de las sesiones competidoras y devuelve el error 1205 a la víctima.
Detectar un bloqueo
Comprueba si un error de SQL Server es un bloqueo (error 1205).
func isDeadlock(err error) bool {
var mssqlErr mssql.Error
if errors.As(err, &mssqlErr) {
return mssqlErr.Number == 1205
}
return false
}
Reintentar transacciones tras los bloqueos
Cuando se produce un bloqueo dentro de una transacción, el servidor reverte toda la transacción. Debes intentar de nuevo la transacción completa, no solo la declaración fallida:
func transferInventory(ctx context.Context, db *sql.DB, productID, fromLocationID, toLocationID int, qty int) error {
return RetryFunc(ctx, DefaultRetryConfig, func(ctx context.Context) error {
tx, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelReadCommitted,
})
if err != nil {
return err
}
defer tx.Rollback()
_, err = tx.ExecContext(ctx,
"UPDATE Production.ProductInventory SET Quantity = Quantity - @qty WHERE ProductID = @pid AND LocationID = @lid",
sql.Named("qty", qty),
sql.Named("pid", productID),
sql.Named("lid", fromLocationID))
if err != nil {
return err
}
_, err = tx.ExecContext(ctx,
"UPDATE Production.ProductInventory SET Quantity = Quantity + @qty WHERE ProductID = @pid AND LocationID = @lid",
sql.Named("qty", qty),
sql.Named("pid", productID),
sql.Named("lid", toLocationID))
if err != nil {
return err
}
return tx.Commit()
})
}
Tip
Reduce los bloqueos accediendo a las tablas en un orden coherente en todas las transacciones y manteniendo las transacciones cortas.
Reintentar es la respuesta correcta en el código de la aplicación, pero los bloqueos repetidos en la misma consulta indican un problema de diseño. Utiliza el grafo de deadlock de SQL Server (capturado mediante Eventos Extendidos o la sesión de salud del sistema) para identificar las sentencias y tipos de bloqueo en competencia. Para una guía completa sobre el análisis y prevención de bloqueos, consulta la guía de bloqueos. Para estrategias de gestión de bloqueos específicas de transacciones, véase Gestión de bloqueos.
Gestionar el agotamiento del pool de conexiones
Cuando todas las conexiones del grupo están en uso y se alcanza MaxOpenConns, las nuevas llamadas quedan bloqueadas hasta que haya una conexión disponible o caduque el plazo del contexto. Esta situación se manifiesta como solicitudes lentas o errores de caducidad del contexto, no como errores explícitos de agotamiento del grupo.
Detectar presión de la piscina
Supervisa las estadísticas del pool y genera una alerta cuando aumenten los recuentos de espera.
func monitorPool(ctx context.Context, db *sql.DB) {
ticker := time.NewTicker(10 * time.Second)
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 pressure: open=%d inUse=%d idle=%d newWaits=%d waitDuration=%v",
stats.OpenConnections, stats.InUse, stats.Idle,
newWaits, stats.WaitDuration)
}
}
}
}
Causas habituales y sus soluciones
| Síntoma | Causa | Solución |
|---|---|---|
WaitCount aumenta de forma constante |
MaxOpenConns es demasiado bajo |
Aumenta MaxOpenConns para ajustarlo a tu nivel de concurrencia. |
InUse es igual a MaxOpenConns durante periodos prolongados |
Las conexiones no se devuelven al grupo | Cierra *sql.Rows, confirma o revierte *sql.Tx y cierra *sql.Conn de inmediato. |
OpenConnections sigue creciendo |
Las conexiones se pierden más rápido de lo que MaxIdleConns puede reciclarlas |
Establezca ConnMaxLifetime y ConnMaxIdleTime para limitar la antigüedad de la conexión. |
| Se superó el tiempo límite del contexto al realizar consultas | La piscina está saturada y los llamantes esperan demasiado | Aumentar el tamaño del pool, reducir el tiempo de ejecución de consultas o añadir tiempos de espera para las consultas. |
Gestionar errores específicos de SQL Server
Violaciones de restricciones
Las violaciones de clave única y clave externa son errores permanentes que indican un problema lógico en la aplicación:
func isUniqueViolation(err error) bool {
var mssqlErr mssql.Error
if errors.As(err, &mssqlErr) {
return mssqlErr.Number == 2627 || // Unique constraint violation
mssqlErr.Number == 2601 // Unique index violation
}
return false
}
func isForeignKeyViolation(err error) bool {
var mssqlErr mssql.Error
if errors.As(err, &mssqlErr) {
return mssqlErr.Number == 547 // FK constraint violation
}
return false
}
Patrón upsert con detección de conflictos
Utiliza una MERGE sentencia para insertar o actualizar una fila de forma atómica:
func upsertDepartment(ctx context.Context, db *sql.DB, id int, name, groupName string) error {
_, err := db.ExecContext(ctx, `
MERGE INTO HumanResources.Department AS target
USING (SELECT @id AS DepartmentID, @name AS Name, @grp AS GroupName) AS source
ON target.DepartmentID = source.DepartmentID
WHEN MATCHED THEN
UPDATE SET Name = source.Name, GroupName = source.GroupName
WHEN NOT MATCHED THEN
INSERT (Name, GroupName) VALUES (source.Name, source.GroupName);`,
sql.Named("id", id),
sql.Named("name", name),
sql.Named("grp", groupName))
return err
}
Errores de permisos
Detectar códigos de error comunes de “permiso denegado” para proporcionar un mensaje claro a quien realiza la llamada:
func isPermissionError(err error) bool {
var mssqlErr mssql.Error
if errors.As(err, &mssqlErr) {
return mssqlErr.Number == 229 || // SELECT permission denied
mssqlErr.Number == 230 || // Column permission denied
mssqlErr.Number == 262 || // CREATE permission denied
mssqlErr.Number == 300 || // VIEW permission denied
mssqlErr.Number == 15247 // User doesn't have permission
}
return false
}
Administra sql.ErrNoRows
sql.ErrNoRowsno es un error de SQL Server. El QueryRowContext.Scan método lo devuelve cuando la consulta no devuelve filas. Manéjalo explícitamente para distinguir "no encontrado" de los errores reales:
func getEmployee(ctx context.Context, db *sql.DB, id int) (*Employee, error) {
var emp Employee
err := db.QueryRowContext(ctx,
"SELECT TOP (1) BusinessEntityID, FirstName + ' ' + LastName AS Name, CountryRegionName AS Location FROM Sales.vSalesPerson WHERE BusinessEntityID = @p1",
sql.Named("p1", id)).Scan(&emp.Id, &emp.Name, &emp.Location)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil // Not found, not an error.
}
if err != nil {
return nil, fmt.Errorf("query employee %d: %w", id, err)
}
return &emp, nil
}
Envuelve los errores con contexto
Añade contexto a los errores para que los llamantes puedan entender dónde ocurrió el fallo:
func getEmployeesByLocation(ctx context.Context, db *sql.DB, location string) ([]Employee, error) {
rows, err := db.QueryContext(ctx,
"SELECT BusinessEntityID, FirstName + ' ' + LastName AS Name, CountryRegionName AS Location FROM Sales.vSalesPerson WHERE CountryRegionName = @p1",
sql.Named("p1", location))
if err != nil {
return nil, fmt.Errorf("query employees by location %q: %w", location, err)
}
defer rows.Close()
var employees []Employee
for rows.Next() {
var emp Employee
if err := rows.Scan(&emp.Id, &emp.Name, &emp.Location); err != nil {
return nil, fmt.Errorf("scan employee row: %w", err)
}
employees = append(employees, emp)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate employee rows: %w", err)
}
return employees, nil
}
El uso %w preserva la cadena de errores, de modo que los llamantes pueden seguir usando errors.As e errors.Is inspeccionar el error subyacente.
Lista de comprobación para el manejo de errores
| Area | Recommendation |
|---|---|
| Aserción de tipos | Declara var mssqlErr mssql.Error, y luego úsate errors.As(err, &mssqlErr) para acceder a los campos de error de SQL Server. |
| Detección de errores transitorios | Clasifica los errores por número y gravedad antes de decidir si intentar de nuevo. |
| Lógica de reintento | Utilizar retroceso exponencial con fluctuación. Establece un número máximo de intentos y un tiempo muerto total según el contexto. |
| Interbloqueos | Reintentar toda la transacción, no instrucciones individuales. Reduce los bloqueos accediendo a las tablas de forma constante. |
| Agotamiento en la piscina | Monitoriza db.Stats() y establece plazos contextuales en todas las llamadas a bases de datos. |
| ErrNoRows | Maneja sql.ErrNoRows explícitamente para QueryRowContext. No es un error del servidor. |
| Envoltura de errores | Usa fmt.Errorf con %w para añadir contexto sin perder la cadena de errores. |
| Violaciones de restricciones | Comprueba los números de error 2627, 2601 (único) y 547 (clave extranjera) para gestionar los conflictos con elegancia. |