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

> Learn how go-sql-driver/mysql handles network failures with ErrBadConn. Discover when this error is returned and how it ensures connection pool reliability for your Go applications.

- Repository: [Go SQL Drivers/mysql](https://github.com/go-sql-driver/mysql)
- Tags: internals
- Published: 2026-03-02

---

**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`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/connection.go) lines 44-45), preventing operations on known-dead sockets.

## Specific Conditions That Trigger ErrBadConn

### Write Failures Before Data Transmission

In [`packets.go`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/errors.go) lines 33-38). The caller then invokes `mc.markBadConn(err)` in [`connection.go`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/errors.go)**: Declares the internal `errBadConnNoWrite` sentinel and documents its conversion semantics to the standard driver error.
- **[`statement.go`](https://github.com/go-sql-driver/mysql/blob/main/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:

```go
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`](https://github.com/go-sql-driver/mysql/blob/main/packets.go) (lines 53-57) demonstrates how the driver handles zero-byte write scenarios before converting to `driver.ErrBadConn`:

```go
// 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`](https://github.com/go-sql-driver/mysql/blob/main/packets.go):

```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`](https://github.com/go-sql-driver/mysql/blob/main/connection.go) (state checks and `markBadConn`), [`packets.go`](https://github.com/go-sql-driver/mysql/blob/main/packets.go) (I/O error detection and `handleErrorPacket`), and [`errors.go`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/errors.go) that indicates a write failure occurred before any bytes were transmitted to the server. The `markBadConn` function in [`connection.go`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/connection.go), and `Exec`/`Query` in [`statement.go`](https://github.com/go-sql-driver/mysql/blob/main/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.