MySQL 连接

MySQL 是 GORM 最常用的数据库,没有之一。Go 生态里的 MySQL 驱动经过多年打磨,稳定性和性能都经得起考验。

驱动安装

GORM v2 的 MySQL 驱动独立维护,需要单独安装:

go get -u gorm.io/driver/mysql

注意包路径是 gorm.io/driver/mysql,不是 github.com/go-sql-driver/mysql。后者是底层驱动,前者在它基础上封装了 GORM 需要的接口。

基本连接

最简单的连接示例:

package main

import (
    "fmt"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func main() {
    dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        panic("连接失败")
    }
    fmt.Println("连接成功")
}

DSN 参数详解

MySQL 的 DSN 支持丰富的参数,合理配置能避免很多坑:

username:password@protocol(address)/dbname?param1=value1&param2=value2

认证部分

  • username - 数据库用户名
  • password - 密码,含特殊字符需要 URL 编码

协议部分

  • tcp(host:port) - TCP 连接,最常用
  • unix(/path/to/socket) - Unix socket,本地性能更好

数据库部分

  • /dbname - 目标数据库名

常用参数

参数说明推荐值
charset字符集utf8mb4
parseTime自动解析时间类型True
loc时区Local 或 Asia/Shanghai
timeout连接超时10s
readTimeout读超时30s
writeTimeout写超时30s
collation排序规则utf8mb4_general_ci

一个生产级别的 DSN:

dsn := "app_user:P@ssw0rd@tcp(db.example.com:3306)/myapp" +
    "?charset=utf8mb4" +
    "&parseTime=True" +
    "&loc=Asia%2FShanghai" +
    "&timeout=10s" +
    "&readTimeout=30s" +
    "&writeTimeout=30s"

密码特殊字符处理

密码包含 @:/ 等特殊字符时,必须 URL 编码:

import "net/url"

password := "P@ss:word/123"
encodedPassword := url.QueryEscape(password)
dsn := fmt.Sprintf("root:%s@tcp(127.0.0.1:3306)/test", encodedPassword)

不编码的话,解析器会把 @ 当成分隔符,导致连接失败。

MySQL 特定配置

mysql.Config 结构体提供 MySQL 专属的配置项:

import "gorm.io/driver/mysql"

mysqlConfig := mysql.Config{
    DSN:                       dsn,
    DefaultStringSize:         256,
    DisableDatetimePrecision:  true,
    DontSupportRenameIndex:    true,
    DontSupportRenameColumn:   true,
    SkipInitializeWithVersion: false,
}

db, err := gorm.Open(&mysqlConfig, &gorm.Config{})

DefaultStringSize

字符串字段默认长度。GORM 迁移时,没有指定长度的 string 类型字段会使用这个值。默认 191,某些场景可能太小。

DisableDatetimePrecision

禁用 datetime 精度。MySQL 5.6.4 之前不支持微秒,老版本数据库需要设为 true

DontSupportRenameIndex

禁用索引重命名。MySQL 5.7 之前不支持 ALTER TABLE ... RENAME INDEX,老版本设为 true 会用 drop + create 替代。

DontSupportRenameColumn

禁用列重命名。MySQL 8 之前不支持 ALTER TABLE ... RENAME COLUMN,老版本设为 true 会用 change 替代。

SkipInitializeWithVersion

跳过版本检测。默认情况下,GORM 会查询 MySQL 版本来决定支持哪些特性。

已有连接复用

如果项目已经有 *sql.DB 对象,可以复用它:

import (
    "database/sql"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func ConnectFromExisting(sqlDB *sql.DB) (*gorm.DB, error) {
    return gorm.Open(mysql.New(mysql.Config{
        Conn: sqlDB,
    }), &gorm.Config{})
}

这种方式适合迁移老项目,或者需要精细控制底层连接的场景。

连接池配置

MySQL 连接是昂贵的资源,必须配置连接池:

sqlDB, err := db.DB()
if err != nil {
    panic(err)
}

sqlDB.SetMaxOpenConns(100)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(time.Hour)
sqlDB.SetConnMaxIdleTime(time.Minute * 10)

这些参数没有银弹,需要根据实际负载调整。一般原则:

  • MaxOpenConns 不超过数据库 max_connections 的 80%
  • MaxIdleConns 设为 MaxOpenConns 的 10%-25%
  • ConnMaxLifetime 比数据库 wait_timeout

字符集陷阱

MySQL 的 utf8 不是真正的 UTF-8,它只支持 3 字节字符。emoji 和部分生僻字会存储失败。

必须使用 utf8mb4

dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4"

数据库和表也要确保是 utf8mb4

CREATE DATABASE myapp CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

时区问题

parseTime=True 让 GORM 自动把 MySQL 的 DATETIME 转成 Go 的 time.Time。但时区不对会导致时间偏差:

loc=Local

使用本地时区,最简单。如果服务器和数据库时区一致,推荐这个。

loc=Asia%2FShanghai

显式指定时区,更可控。URL 编码后的 / 变成 %2F

SSL 连接

生产环境数据库通常要求 SSL:

dsn := "user:pass@tcp(db.example.com:3306)/dbname" +
    "?tls=true" +
    "&timeout=10s"

自定义证书:

import "github.com/go-sql-driver/mysql"

tlsConfig := &tls.Config{
    RootCAs: rootCertPool,
    ClientCerts: []tls.Certificate{clientCert},
}
mysql.RegisterTLSConfig("custom", tlsConfig)

dsn := "user:pass@tcp(db.example.com:3306)/dbname?tls=custom"

常见错误

连接拒绝

Error 1045: Access denied for user 'root'@'localhost'

用户名或密码错误,检查 DSN 中的认证信息。

数据库不存在

Error 1049: Unknown database 'myapp'

先创建数据库,或者 DSN 中不指定数据库名,连接后再创建。

字符集错误

Error 1366: Incorrect string value: '\xF0\x9F...'

emoji 等 4 字节字符,数据库或表不是 utf8mb4 编码。

连接超时

connection refused

检查 MySQL 服务是否启动,端口是否正确,防火墙是否放行。

小结

MySQL 连接配置看似简单,坑却不少。字符集、时区、连接池是三个最容易出问题的地方。配置对了,后续开发才能顺畅。下一章看 PostgreSQL。