# How to Register and Utilize Custom Dial Functions for Advanced MySQL Connection Setup in Go

> Learn to register and use custom dial functions for advanced MySQL connections in Go. Leverage mysql.RegisterDialContext or Config.DialFunc for flexible connection setup with driver mysql.

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

---

**Use `mysql.RegisterDialContext()` to register a global custom dialer referenced by network name in the DSN, or set `Config.DialFunc` directly for per-connector overrides that don't require global registration.**

The `go-sql-driver/mysql` package provides a pluggable architecture allowing developers to register and utilize custom dial functions for advanced connection setup when standard TCP transports are insufficient. Whether routing through TLS-terminating proxies, establishing SSH tunnels, or injecting connection-level instrumentation, the driver exposes two distinct APIs—global registration and per-connector configuration—for customizing network establishment.

## Understanding the Dial Function Architecture

The driver defines two function signatures in [`driver.go`](https://github.com/go-sql-driver/mysql/blob/main/driver.go) to handle network connections, supporting both modern context-aware workflows and legacy code:

- **`DialContextFunc`** – The modern signature accepting context for cancellation and timeouts (lines 38-39)
- **`DialFunc`** – The legacy signature maintained for backward compatibility (lines 31-35)

```go
// driver.go
type DialContextFunc func(ctx context.Context, addr string) (net.Conn, error)
type DialFunc func(addr string) (net.Conn, error)

```

### Global Registry Architecture

The driver maintains a thread-safe global map named `dials` that stores custom dialers keyed by network names. Registration functions acquire locks via `dialsLock` to ensure safe concurrent access across goroutines.

```go
// driver.go – Registration API (lines 46-55, 67-75)
func RegisterDialContext(net string, dial DialContextFunc)
func RegisterDial(network string, dial DialFunc) // Legacy wrapper
func DeregisterDialContext(net string)

```

### Per-Connector Configuration

For scenarios requiring isolation from global state, the `Config` struct in [`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go) exposes a `DialFunc` field that supersedes the global registry for individual connectors only (lines 57-58):

```go
// dsn.go
type Config struct {
    DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
    // ... other fields
}

```

## Registering a Global Custom Dialer

To register a custom dial function globally, invoke `RegisterDialContext` before opening any database connections. The network name you register becomes the protocol identifier in your DSN.

```go
// Register a custom dialer with specific timeout and keepalive settings
mysql.RegisterDialContext("customtcp", func(ctx context.Context, addr string) (net.Conn, error) {
    d := net.Dialer{
        Timeout:   5 * time.Second,
        KeepAlive: 30 * time.Second,
    }
    return d.DialContext(ctx, "tcp", addr)
})

```

Once registered, reference the custom dialer in your DSN using the format `user:pass@networkname(host:port)/dbname`:

```go
dsn := "user:password@customtcp(127.0.0.1:3306)/mydb?timeout=30s"
db, err := sql.Open("mysql", dsn)

```

The driver looks up the network name (`customtcp`) in the global `dials` map during connection establishment.

## Implementing Per-Connector Dial Overrides

When global registration is inappropriate—such as in multi-tenant applications requiring different transport configurations per connection pool—set `Config.DialFunc` directly without registering a network name.

```go
cfg, _ := mysql.ParseDSN("user:pass@tcp(127.0.0.1:3306)/mydb")
cfg.DialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) {
    d := net.Dialer{Timeout: 5 * time.Second}
    return d.DialContext(ctx, network, addr)
}

connector, _ := mysql.NewConnector(cfg)
db := sql.OpenDB(connector)

```

This approach requires no special network name in the DSN and affects only connections created through that specific connector, leaving the global `dials` registry untouched.

## Connection Establishment Priority

According to the implementation in [`connector.go`](https://github.com/go-sql-driver/mysql/blob/main/connector.go) (lines 98-106), the driver resolves dial functions in the following strict priority order when establishing a connection:

1. **Per-connector override**: If `c.cfg.DialFunc` is non-nil, invoke it with `c.cfg.DialFunc(dctx, mc.cfg.Net, mc.cfg.Addr)`
2. **Global registry lookup**: Check the `dials` map using `mc.cfg.Net` as the key; if found, call the registered `DialContextFunc`
3. **Standard net.Dialer**: Fallback to the default Go network dialer with default timeouts

```go
// connector.go – dialing logic priority
if c.cfg.DialFunc != nil {
    mc.netConn, err = c.cfg.DialFunc(dctx, mc.cfg.Net, mc.cfg.Addr)
} else {
    dialsLock.RLock()
    dial, ok := dials[mc.cfg.Net]
    dialsLock.RUnlock()
    if ok {
        mc.netConn, err = dial(dctx, mc.cfg.Addr)
    } else {
        nd := net.Dialer{}
        mc.netConn, err = nd.DialContext(dctx, mc.cfg.Net, mc.cfg.Addr)
    }
}

```

## Practical Implementation Examples

### Example 1: Global Registration with Custom Socket Options

This example demonstrates registering a dialer that enforces specific timeout and keepalive settings, then referencing it via DSN:

```go
package main

import (
    "context"
    "database/sql"
    "net"
    "time"
    
    "github.com/go-sql-driver/mysql"
)

func main() {
    // Register custom dialer globally (driver.go lines 46-55)
    mysql.RegisterDialContext("slownet", func(ctx context.Context, addr string) (net.Conn, error) {
        d := net.Dialer{
            Timeout:   5 * time.Second,
            KeepAlive: 30 * time.Second,
        }
        // Simulate connection latency or proxy overhead
        time.Sleep(2 * time.Second)
        return d.DialContext(ctx, "tcp", addr)
    })
    
    // Use the registered network name in DSN
    dsn := "user:password@slownet(127.0.0.1:3306)/mydb?timeout=30s"
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    if err := db.Ping(); err != nil {
        panic(err)
    }
}

```

### Example 2: Per-Connector TLS Wrapper

For connections requiring custom TLS setup without polluting the global registry:

```go
cfg, _ := mysql.ParseDSN("user:pass@tcp(db.example.com:3306)/production")
cfg.DialFunc = func(ctx context.Context, network, addr string) (net.Conn, error) {
    d := net.Dialer{Timeout: 10 * time.Second}
    rawConn, err := d.DialContext(ctx, network, addr)
    if err != nil {
        return nil, err
    }
    // Wrap with custom TLS configuration
    // tlsConn := tls.Client(rawConn, tlsConfig)
    // return tlsConn, nil
    return rawConn, nil
}

connector, _ := mysql.NewConnector(cfg)
db := sql.OpenDB(connector)

```

### Example 3: Dynamic Dialer Injection with BeforeConnect

Use the `BeforeConnect` hook to inject dial logic immediately before connection establishment:

```go
cfg, _ := mysql.ParseDSN("user:pass@tcp(127.0.0.1:3306)/")
cfg.Apply(mysql.BeforeConnect(func(ctx context.Context, c *mysql.Config) error {
    c.DialFunc = func(ctx context.Context, net, addr string) (net.Conn, error) {
        return net.DialTimeout(net, addr, 10*time.Second)
    }
    return nil
}))

connector, _ := mysql.NewConnector(cfg)
db := sql.OpenDB(connector)

```

## Summary

- **`RegisterDialContext`** – Use for global dialer registration referenced by network name in DSNs; stored in thread-safe `dials` map in [`driver.go`](https://github.com/go-sql-driver/mysql/blob/main/driver.go) (lines 46-55)
- **`Config.DialFunc`** – Use for per-connector overrides that don't require global state or special DSN network names; defined in [`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go) (lines 57-58) and checked first in [`connector.go`](https://github.com/go-sql-driver/mysql/blob/main/connector.go) (lines 98-100)
- **Priority Order** – Per-connector `DialFunc` takes precedence over global registry lookups, which take precedence over the standard `net.Dialer` fallback
- **Legacy Support** – `RegisterDial` wraps `RegisterDialContext` internally (lines 67-75) to maintain backward compatibility with non-context dial functions
- **DSN Format** – Custom network names appear before the parenthesis: `user@networkname(host:port)/db`

## Frequently Asked Questions

### What is the difference between RegisterDial and RegisterDialContext?

`RegisterDial` accepts the legacy `DialFunc` signature (`func(addr string) (net.Conn, error)`) and wraps it to implement the modern `DialContextFunc` interface, effectively ignoring the context parameter. `RegisterDialContext` accepts the full `DialContextFunc` signature (`func(ctx context.Context, addr string) (net.Conn, error)`), allowing proper cancellation and timeout handling through context propagation. According to the source in [`driver.go`](https://github.com/go-sql-driver/mysql/blob/main/driver.go) lines 67-75, `RegisterDial` simply adapts the legacy function to call `RegisterDialContext` internally.

### Can I use a custom dialer for just one database connection without affecting others?

Yes. Instead of using `RegisterDialContext` which affects all connections using that network name globally, create a custom `Config` and set its `DialFunc` field directly. As implemented in [`connector.go`](https://github.com/go-sql-driver/mysql/blob/main/connector.go) lines 98-100, this per-connector function takes precedence over the global registry, ensuring only connections created through that specific configuration use your custom dial logic while other `sql.DB` instances use standard or globally registered dialers.

### How does the driver choose which dial function to use?

The driver follows a strict resolution order defined in [`connector.go`](https://github.com/go-sql-driver/mysql/blob/main/connector.go): first checking `Config.DialFunc` for per-connector overrides (lines 98-100), then looking up the network name in the global `dials` map populated by `RegisterDialContext` (lines 101-106), and finally falling back to the standard library's `net.Dialer`. This hierarchy ensures maximum flexibility while maintaining backward compatibility with standard DSN configurations.

### What network name should I use in the DSN when registering a custom dialer?

Any unique string identifier works as the network name (e.g., `mydial`, `proxy`, `customtcp`). This identifier becomes the protocol portion of your DSN: `user:pass@mydial(localhost:3306)/dbname`. The driver uses this string as the lookup key in the `dials` map, as shown in [`connector.go`](https://github.com/go-sql-driver/mysql/blob/main/connector.go) lines 101-106. Choose descriptive names that indicate the transport mechanism or specific configuration being applied.