# How the `rejectReadOnly` DSN Parameter Prevents Accidental Writes to Read‑Only Replicas

> Learn how the rejectReadOnly DSN parameter for go-sql-driver/mysql prevents accidental writes to read-only replicas by detecting errors and retrying on a writable primary.

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

---

**The `rejectReadOnly` DSN parameter detects specific MySQL error codes indicating a read‑only connection, immediately closes the offending connection to signal `driver.ErrBadConn`, and triggers the Go `database/sql` pool to retry on a writable primary, eliminating silent write failures in replica environments.**

The `go-sql-driver/mysql` package provides a critical safety mechanism for applications running against MySQL read‑only replicas or Aurora clusters. When enabled, the `rejectReadOnly` parameter acts as a fail‑fast switch that automatically handles fail‑over scenarios by detecting read‑only session states and forcing connection recycling. This prevents applications from unintentionally continuing with stale read‑only connections that would reject subsequent write operations.

## How `rejectReadOnly` Works

The implementation follows a three‑stage pipeline: configuration parsing, error code detection, and forced connection recycling.

### DSN Configuration in [`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go)

When the DSN string contains `rejectReadOnly=true`, the parser in [`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go) (line 73) populates the `Config.RejectReadOnly` boolean field. This configuration flag remains active for the lifetime of the connection pool, instructing the driver to monitor for read‑only error conditions on every query execution.

### Error Code Detection in [`packets.go`](https://github.com/go-sql-driver/mysql/blob/main/packets.go)

After each statement execution, the driver examines MySQL error packets in [`packets.go`](https://github.com/go-sql-driver/mysql/blob/main/packets.go) (lines 998‑1014). The implementation specifically watches for three error codes that indicate a read‑only session:

- **1792** – `ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION`
- **1290** – `ER_OPTION_PREVENTS_STATEMENT` (common during Aurora fail‑over)
- **1836** – `ER_READ_ONLY_MODE`

If any of these codes are received **and** `RejectReadOnly` is enabled, the driver executes `mc.Close()` to terminate the connection immediately, then returns `driver.ErrBadConn` instead of propagating the raw MySQL error.

### Automatic Connection Recycling

Returning `driver.ErrBadConn` signals the Go `database/sql` package to discard the current connection from the pool and establish a fresh one for the next operation. In typical replica setups with proxy or DNS‑based routing, the new connection resolves to a writable primary, allowing the retried write to succeed without application intervention.

## Configuration and DSN Setup

To enable the protection, append `rejectReadOnly=true` to your DSN string:

```go
// DSN with rejectReadOnly enabled – safe for read‑only replica pools
dsn := "user:password@tcp(127.0.0.1:3306)/dbname?rejectReadOnly=true"
db, err := sql.Open("mysql", dsn)
if err != nil { 
    log.Fatal(err) 
}

// Example: a write that would otherwise hit a read‑only replica
_, err = db.Exec("INSERT INTO orders (id, total) VALUES (1, 42)")
if err != nil {
    // With rejectReadOnly the driver will have already closed the read‑only
    // connection and retried on a writable one, so this error only occurs on
    // genuine failures (e.g., syntax error).
    log.Fatal(err)
}

```

## Error Detection Logic in [`packets.go`](https://github.com/go-sql-driver/mysql/blob/main/packets.go)

The core detection logic resides in the packet handling layer. When the server returns an error packet, the driver inspects the error number before returning it to the caller:

1. Compare the MySQL error code against the read‑only set (1792, 1290, 1836).
2. Check if `mc.cfg.RejectReadOnly` is `true`.
3. If both conditions match, mark the connection as closed via `mc.Close()`.
4. Return `driver.ErrBadConn` to force pool eviction.

This approach ensures that the application never retains a connection in an ambiguous read‑only state.

## Practical Implementation Examples

The driver's test suite in [`driver_test.go`](https://github.com/go-sql-driver/mysql/blob/main/driver_test.go) (lines 291‑322) demonstrates this recovery mechanism:

```go
runTests(t, dsn+"&rejectReadOnly=true", func(dbt *DBTest) {
    dbt.mustExec("CREATE TABLE test (value BOOL)")
    // Switch the session to read‑only.
    dbt.mustExec("SET SESSION TRANSACTION READ ONLY")
    // The following drop will trigger the rejectReadOnly logic,
    // causing the driver to close the connection and let database/sql
    // obtain a writable one before retrying.
    dbt.mustExec("DROP TABLE test")
})

```

Without `rejectReadOnly`, the `DROP TABLE` statement would return an error and leave the connection in a read‑only state. With the flag enabled, the driver automatically recovers by closing the connection and allowing the pool to acquire a writable replacement.

## Summary

- The `rejectReadOnly` DSN parameter configures the driver to monitor for specific MySQL read‑only error codes (1792, 1290, and 1836) as implemented in [`packets.go`](https://github.com/go-sql-driver/mysql/blob/main/packets.go).
- When detected, the driver calls `mc.Close()` and returns `driver.ErrBadConn` to force immediate connection eviction.
- This mechanism leverages the standard Go `database/sql` connection pool behavior to obtain a fresh connection, typically routing to a writable primary in replica environments.
- The feature provides automatic, transparent fail‑over for Aurora clusters and other read‑only replica setups without requiring application-level retry logic.

## Frequently Asked Questions

### What MySQL error codes trigger the `rejectReadOnly` behavior?

The driver monitors for three specific error codes: **1792** (`ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION`), **1290** (`ER_OPTION_PREVENTS_STATEMENT`), and **1836** (`ER_READ_ONLY_MODE`). These codes indicate that the current session is operating in read‑only mode, typically due to server configuration or Aurora fail‑over events.

### How does `rejectReadOnly` differ from standard error handling?

Without `rejectReadOnly`, the driver returns the raw MySQL error to the application and retains the connection in the pool, leaving it in a read‑only state for subsequent queries. With `rejectReadOnly` enabled, the driver proactively closes the connection and returns `driver.ErrBadConn`, forcing `database/sql` to discard the connection and establish a new one before retrying the operation.

### Is `rejectReadOnly` necessary for Amazon Aurora MySQL clusters?

Yes. Aurora clusters frequently perform fail‑over operations that can leave existing connections pointing to read‑only replicas. Enabling `rejectReadOnly=true` ensures that writes encountering the **1290** error code (`ER_OPTION_PREVENTS_STATEMENT`) automatically trigger connection recycling, allowing the application to seamlessly redirect to the new primary writer without manual intervention.

### What happens if `rejectReadOnly` is false and a write hits a read‑only replica?

The MySQL server returns error 1792 or 1290, which the driver passes directly to the application as a standard error. The connection remains in the pool in a read‑only state, meaning subsequent write attempts on that same connection will continue to fail until the application manually handles the error or the connection is naturally recycled.