How go-sql-driver/mysql Manages Connection Timeout, readTimeout, and writeTimeout Settings

The go-sql-driver/mysql driver applies timeout as a context deadline during TCP dial operations, while readTimeout and writeTimeout are enforced as per-operation socket deadlines via SetReadDeadline and SetWriteDeadline on the established connection.

The go-sql-driver/mysql package provides granular control over network timeouts through three distinct DSN parameters. Understanding how the driver differentiates between connection establishment, read operations, and write operations helps prevent hanging goroutines and ensures resilient database interactions in production environments.

Parsing Timeout Parameters from the DSN

The DSN parser in dsn.go extracts timeout values and populates the corresponding fields in the Config struct. Each parameter maps to a specific phase of the connection lifecycle.

Connection Timeout (timeout)

The timeout parameter controls how long the driver waits for the TCP handshake to complete. In dsn.go, the parser assigns this value to cfg.Timeout:

case "timeout":
    cfg.Timeout, err = time.ParseDuration(value)

Source: [dsn.go](https://github.com/go-sql-driver/mysql/blob/master/dsn.go)

Read Timeout (readTimeout)

The readTimeout parameter sets a deadline for every read operation from the server. The parser stores this in cfg.ReadTimeout:

case "readTimeout":
    cfg.ReadTimeout, err = time.ParseDuration(value)

When serializing the DSN for logging, non-zero values are formatted back into the connection string:

if cfg.ReadTimeout > 0 {
    writeDSNParam(&buf, &hasParam, "readTimeout", cfg.ReadTimeout.String())
}

Source: [dsn.go](https://github.com/go-sql-driver/mysql/blob/master/dsn.go)

Write Timeout (writeTimeout)

Similarly, writeTimeout governs how long the driver waits to send data to the server. The parser handles it identically to the read timeout:

case "writeTimeout":
    cfg.WriteTimeout, err = time.ParseDuration(value)

Source: [dsn.go](https://github.com/go-sql-driver/mysql/blob/master/dsn.go)

Applying Connection Timeout During Dial

When the driver establishes a new connection, connector.go applies the Config.Timeout value by wrapping the parent context with context.WithTimeout. This creates a hard deadline for the TCP dial operation:

dctx := ctx
if mc.cfg.Timeout > 0 {
    var cancel context.CancelFunc
    dctx, cancel = context.WithTimeout(ctx, mc.cfg.Timeout)
    defer cancel()
}

The resulting dctx is passed to the net.Dialer or a custom DialFunc. If the TCP handshake exceeds the configured duration, the context cancels and the driver returns an error immediately rather than hanging indefinitely.

Source: [connector.go](https://github.com/go-sql-driver/mysql/blob/master/connector.go)

Enforcing Read and Write Timeouts on Established Connections

Once the TCP connection is established, the driver no longer uses context cancellation for I/O. Instead, it applies per-operation deadlines directly to the socket using the standard library's net.Conn methods.

Per-Operation Read Deadlines

The mysqlConn struct provides readWithTimeout, which sets a read deadline before every socket read:

func (mc *mysqlConn) readWithTimeout(b []byte) (int, error) {
    to := mc.cfg.ReadTimeout
    if to > 0 {
        if err := mc.netConn.SetReadDeadline(time.Now().Add(to)); err != nil {
            return 0, err
        }
    }
    return mc.netConn.Read(b)
}

This helper is invoked throughout packets.go whenever the driver reads server responses. If the server fails to send data within the readTimeout window, the operation returns a timeout error.

Source: [connection.go](https://github.com/go-sql-driver/mysql/blob/master/connection.go)

Per-Operation Write Deadlines

Similarly, writeWithTimeout applies a write deadline before transmitting data:

func (mc *mysqlConn) writeWithTimeout(b []byte) (int, error) {
    to := mc.cfg.WriteTimeout
    if to > 0 {
        if err := mc.netConn.SetWriteDeadline(time.Now().Add(to)); err != nil {
            return 0, err
        }
    }
    return mc.netConn.Write(b)
}

This ensures that slow network conditions or backpressure from the server cannot cause the client to block indefinitely while sending queries or prepared statements.

Source: [connection.go](https://github.com/go-sql-driver/mysql/blob/master/connection.go)

Complete Working Example

The following example demonstrates how to configure all three timeout parameters in a DSN and how they behave during actual operations:

package main

import (
    "database/sql"
    "fmt"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    // DSN with explicit timeout settings
    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?" +
        "timeout=5s&readTimeout=2s&writeTimeout=3s"

    db, err := sql.Open("mysql", dsn)
    if err != nil {
        panic(err)
    }
    defer db.Close()

    // The dial timeout (5s) is applied when the first connection
    // is opened (e.g. on Ping or first Exec/Query).
    if err := db.Ping(); err != nil {
        fmt.Printf("dial timeout or connectivity problem: %v\n", err)
        return
    }

    // Subsequent queries use the read/write deadlines.
    // The driver will abort a read if the server does not send data
    // within 2 seconds, and abort a write if the client cannot
    // flush data within 3 seconds.
    rows, err := db.Query("SELECT SLEEP(4)") // exceeds readTimeout → error
    if err != nil {
        fmt.Printf("query failed (likely read timeout): %v\n", err)
        return
    }
    rows.Close()
}

In this scenario:

  • db.Ping() fails if the TCP handshake exceeds 5 seconds.
  • SELECT SLEEP(4) fails because the server waits 4 seconds before responding, exceeding the 2-second readTimeout.

Summary

  • timeout controls the TCP dial phase via context.WithTimeout in connector.go, limiting how long the driver waits to establish a connection.
  • readTimeout is enforced as a per-operation deadline via SetReadDeadline in connection.go, applied before every socket read in packets.go.
  • writeTimeout is enforced as a per-operation deadline via SetWriteDeadline in connection.go, applied before every socket write.
  • Zero values disable the respective timeouts, allowing operations to block indefinitely until the OS intervenes.

Frequently Asked Questions

What happens if I only set the connection timeout but not read or write timeouts?

If you only configure timeout in the DSN, the driver will enforce a deadline during the initial TCP handshake, but once the connection is established, all subsequent read and write operations can block indefinitely. This means a stalled server or broken network path could cause your application goroutines to hang forever waiting for I/O.

How does readTimeout differ from the connection timeout?

The timeout parameter applies only to the dial phase when the driver establishes the TCP connection via context.WithTimeout in connector.go. In contrast, readTimeout applies to every individual read operation on the already-established socket, setting a deadline via SetReadDeadline before each call to netConn.Read in connection.go.

Can I change these timeouts dynamically after the connection is established?

No, the driver does not support dynamic reconfiguration of timeouts on existing connections. The Config values are read once during connection initialization in connector.go and stored in the mysqlConn struct. To use different timeout values, you must create a new sql.DB instance with a modified DSN string.

Where are the deadlines actually enforced in the MySQL protocol implementation?

The read and write deadlines are enforced in connection.go through the readWithTimeout and writeWithTimeout helper methods. These methods are invoked throughout packets.go whenever the driver needs to read server responses or send client commands, ensuring that every low-level I/O operation respects the configured readTimeout and writeTimeout values.

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 →