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("连接成功")
}
MySQL 的 DSN 支持丰富的参数,合理配置能避免很多坑:
username:password@protocol(address)/dbname?param1=value1¶m2=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.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:
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。