Go MySQL Driver Error Handling Strategy for Network Failures: When and How ErrBadConn Is Returned

The go-sql-driver/mysql returns driver.ErrBadConn whenever a TCP connection becomes unsafe for reuse, signaling database/sql to purge the connection from the pool and retry on a fresh socket.

The error handling strategy in the go-sql-driver/mysql repository centers on the standard driver.ErrBadConn sentinel to manage network failures gracefully. When the driver detects conditions that render a connection unreliable—such as closed sockets, failed writes before data transmission, or read-only failover scenarios—it returns this specific error to trigger automatic connection replacement.

How the Driver Detects Bad Connections

The driver implements a strict hierarchy of checks in connection.go to determine connection viability. At the start of every major API method—including Begin, Prepare, Exec, and Query—the driver verifies the connection state via mc.closed.Load(). If the connection is already marked as closed or cancelled, the method returns driver.ErrBadConn immediately (as seen in connection.go lines 44-45), preventing operations on known-dead sockets.

Specific Conditions That Trigger ErrBadConn

Write Failures Before Data Transmission

In packets.go, the writePacket function distinguishes between partial and complete write failures. When the first write iteration returns zero bytes written, the driver returns the internal sentinel errBadConnNoWrite (defined in errors.go lines 33-38). The caller then invokes mc.markBadConn(err) in connection.go (lines 29-35), which converts this internal error into the public driver.ErrBadConn. This distinction matters because no data reached the server, making the operation safe to retry.

Read Packet Network Errors

The readPacket function in packets.go (lines 52-60) handles network read failures, timeouts, EOF conditions, or malformed packets by immediately closing the underlying net.Conn and returning ErrInvalidConn. Higher-level calls that encounter these errors subsequently translate them into driver.ErrBadConn, ensuring the connection is evicted from the pool when the socket becomes unreadable.

Read-Only Failover Detection

When the server returns error codes 1792, 1290, or 1836 and the RejectReadOnly configuration is enabled, the driver's handleErrorPacket logic in packets.go (lines 100-112) closes the connection and returns driver.ErrBadConn directly. This allows write operations to fail fast on read-only replicas and retry on primary instances.

Safe-to-Retry Command Failures

For commands like COM_STMT_PREPARE that can be replayed safely, the driver returns driver.ErrBadConn immediately if writeCommandPacket fails. The driver treats these commands as idempotent, ensuring that transient network interruptions do not permanently fail the operation while allowing database/sql to retry on a fresh connection.

When ErrBadConn Is NOT Returned

The driver deliberately preserves original errors when the server may have partially processed a request. If writePacket fails after transmitting some bytes but not others, the driver returns the raw error rather than driver.ErrBadConn. This prevents duplicate execution of non-idempotent operations, as retrying could cause the server to apply the same change twice.

Source Code Implementation Details

The error handling logic spans four critical files:

  • connection.go: Contains the markBadConn helper that converts internal errBadConnNoWrite to driver.ErrBadConn, and guards high-level API methods with mc.closed.Load() checks.
  • packets.go: Implements low-level packet I/O via writePacket and readPacket, detects read-only failover conditions in handleErrorPacket, and distinguishes between zero-byte write failures and partial write errors.
  • errors.go: Declares the internal errBadConnNoWrite sentinel and documents its conversion semantics to the standard driver error.
  • statement.go: Propagates driver.ErrBadConn through Exec and Query paths (lines 54-57) when underlying connections are closed.

Practical Code Examples

Handling Automatic Connection Replacement

When using database/sql, the pool automatically discards connections marked with driver.ErrBadConn and establishes new ones:

db, err := sql.Open("mysql", dsn)
if err != nil {
    log.Fatal(err)
}

// The driver returns driver.ErrBadConn for network failures,
// triggering automatic retry on a fresh connection
result, err := db.Exec("UPDATE users SET last_login = NOW() WHERE id = ?", userID)
if err != nil {
    if errors.Is(err, driver.ErrBadConn) {
        // Connection was replaced; application-level retry optional
        log.Println("Network failure detected, connection replaced")
    }
}

Detecting Write Failures Before Transmission

The internal logic in packets.go (lines 53-57) demonstrates how the driver handles zero-byte write scenarios before converting to driver.ErrBadConn:

// This mirrors the internal check in packets.go
func (mc *mysqlConn) writePacket(data []byte) error {
    // Attempt first write
    n, err := mc.netConn.Write(data)
    if err != nil && n == 0 {
        // No bytes sent - safe to mark as bad connection
        return errBadConnNoWrite
    }
    // If n > 0 and err != nil, return original error (partial write)
    return err
}

Configuring Read-Only Failover Handling

Enable RejectReadOnly to force ErrBadConn on read-only errors, as implemented in packets.go:

cfg := mysql.NewConfig()
cfg.RejectReadOnly = true
db, err := sql.Open("mysql", cfg.FormatDSN())
if err != nil {
    log.Fatal(err)
}

// This will return driver.ErrBadConn if the server is read-only,
// allowing retry on a writable primary
_, err = db.Exec("INSERT INTO orders (item) VALUES (?)", "widget")

Summary

  • The driver returns driver.ErrBadConn whenever a TCP connection becomes unusable due to closed sockets, zero-byte write failures, read errors, or read-only failover conditions.
  • File locations: Core logic resides in connection.go (state checks and markBadConn), packets.go (I/O error detection and handleErrorPacket), and errors.go (internal sentinel definitions).
  • Safe retry semantics: The driver only returns ErrBadConn when no data reached the server or when the connection is known to be unusable, preventing duplicate execution of partial operations.
  • Automatic pool management: database/sql automatically evicts connections marked with ErrBadConn and establishes fresh replacements without manual intervention.

Frequently Asked Questions

What is the difference between errBadConnNoWrite and driver.ErrBadConn?

errBadConnNoWrite is an internal sentinel defined in errors.go that indicates a write failure occurred before any bytes were transmitted to the server. The markBadConn function in connection.go (lines 29-35) converts this internal error into the standard driver.ErrBadConn sentinel, which the database/sql package recognizes as a signal to discard the connection from the pool and retry the operation.

Why doesn't the driver return ErrBadConn for all network errors?

The driver preserves original errors for write failures that occur after partial data transmission. When some bytes reach the server before the connection fails (detected in packets.go), retrying the operation could cause duplicate execution. By returning the raw error instead of ErrBadConn, the driver prevents automatic retries that might result in unintended side effects or data corruption.

How does the RejectReadOnly configuration affect error handling?

When RejectReadOnly is enabled and the server returns error codes 1792, 1290, or 1836 (indicating a read-only state), the driver's handleErrorPacket function in packets.go (lines 100-112) closes the connection immediately and returns driver.ErrBadConn. This triggers database/sql to retry the operation on a fresh connection, ideally routing to a writable primary server instead of a read-only replica.

Where does the driver check if a connection is already closed?

Every high-level API method—including Begin, Prepare, Exec in connection.go, and Exec/Query in statement.go (lines 54-57)—checks mc.closed.Load() before executing operations. If this atomic boolean indicates the connection is closed, the method returns driver.ErrBadConn immediately without attempting network I/O.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →