Go 服务在本机连数据库正常,换到服务器就报错,很多时候不是 ORM 写错了,而是连接串、时区、字符集、初始化脚本这几处没有对齐。GoFrame 项目也一样。先别急着改 DAO,先把数据库连接本身查清楚。
这篇只看一个具体问题:MySQL 和 PostgreSQL 在 Go 后台项目里怎么做最小排查。示例命令都可以直接复制执行,输出和你环境不一样没关系,关键是确认配置是否一致。
1. 先把 MySQL DSN 写完整
Go 的 MySQL 驱动默认行为比较保守。后台系统常见的时间字段一般是 datetime、timestamp,如果 DSN 里没有打开 parseTime,扫到 time.Time 时就容易出问题。
一个比较稳的 MySQL DSN 可以这样写:
text
user:password@tcp(127.0.0.1:3306)/app_db?charset=utf8mb4&parseTime=true&loc=Local
如果服务和数据库不在同一台机器,建议先不要写 localhost。在 Linux 上,localhost 可能走 socket,也可能被解析成 IPv6,排查时不如直接写清楚 IP 和端口。
GoFrame 的配置可以放在 manifest/config/config.yaml 这一类配置文件里,写法通常类似:
yaml
database:
default:
link: "mysql:user:password@tcp(127.0.0.1:3306)/app_db?charset=utf8mb4&parseTime=true&loc=Local"
debug: true
这里有三个点最容易漏:
charset=utf8mb4,避免 emoji、特殊符号或部分中文数据写入异常;parseTime=true,让时间字段能正确转成time.Time;loc=Local或明确的时区,避免本地和服务器时间差几个小时。
如果你不确定服务实际读到的是哪份配置,先在启动日志里打印配置路径,或者临时把数据库名改成一个不存在的值,看报错是否变化。这个办法很笨,但能快速排除"改了 A 文件,程序读的是 B 文件"的情况。
2. 用数据库命令确认字符集和时区
不要只看配置文件。配置写对了,数据库实例本身也可能不一致。连上 MySQL 后先查这几项:
sql
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
SELECT NOW(), @@global.time_zone, @@session.time_zone;
期望看到的结果通常是:库、连接、客户端都在 utf8mb4 附近,排序规则用 utf8mb4_0900_ai_ci 或 utf8mb4_general_ci 这类值都可以,关键是别混成 latin1。
如果已有库不是 utf8mb4,先看库级设置:
sql
SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
FROM information_schema.SCHEMATA
WHERE SCHEMA_NAME = 'app_db';
新建库时可以直接写清楚:
sql
CREATE DATABASE app_db
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_0900_ai_ci;
线上库不要看到字符集不一致就立刻 ALTER。先导出备份,再确认表、字段、索引长度和历史数据。排查文章可以写命令,生产环境要慢一点。
3. 初始化脚本失败,先分清"连接失败"和"SQL 失败"
后台管理系统经常会带一份初始化 SQL。比如有的项目会同时提供 mysql_install.sql 和 pgsql_install.sql,方便 MySQL 和 PostgreSQL 两种环境初始化。看到这种文件结构时,可以顺手检查数据库脚本是否按目标数据库拆开维护。
连接失败和 SQL 失败的处理方式不一样。建议先手动执行最小连接命令:
bash
mysql -h 127.0.0.1 -P 3306 -u user -p app_db -e "SELECT NOW();"
如果这一步失败,先查账号、密码、端口、防火墙、授权范围。常见授权问题可以用下面的 SQL 看:
sql
SELECT user, host FROM mysql.user WHERE user = 'user';
SHOW GRANTS FOR 'user'@'%';
如果最小连接没问题,再执行初始化脚本:
bash
mysql -h 127.0.0.1 -P 3306 -u user -p app_db < mysql_install.sql
这一步才报错,通常就是 SQL 方言、表已存在、字段类型或排序规则问题。把报错行号和对应 SQL 贴出来看,比只说"初始化失败"要有效得多。
4. PostgreSQL 重点看 search_path 和时区
PostgreSQL 的坑不太一样。MySQL 里大家习惯把库名写在 DSN 里,PostgreSQL 还要注意 schema。一个常见连接串是:
text
postgres://user:password@127.0.0.1:5432/app_db?sslmode=disable&TimeZone=Asia/Shanghai
连上之后先查三件事:
sql
SELECT NOW();
SELECT current_setting('TimeZone');
SHOW search_path;
如果初始化脚本建表在 public,但程序查询时用了别的 schema,就会出现"表明明存在,程序却说 relation does not exist"。排查时可以直接指定:
sql
SET search_path TO public;
SELECT * FROM public.sys_user LIMIT 1;
如果项目配置里支持 schema,建议显式写清楚。没有特殊隔离需求时,先用 public 跑通,再考虑多 schema。
PostgreSQL 初始化脚本也不要和 MySQL 脚本混用。AUTO_INCREMENT、反引号、部分时间函数、布尔类型写法都可能不兼容。双数据库项目最稳的方式是两份脚本分别维护,而不是靠一份 SQL 到处跑。
5. Go 代码里做一次最小验证
配置和数据库都看过之后,可以写一个很小的启动检查。不要一上来跑完整业务链路,先确认能 ping、能查时间、能读一张基础表。
go
package main
import (
"context"
"time"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
v, err := g.DB().GetValue(ctx, "SELECT NOW()")
if err != nil {
panic(err)
}
g.Log().Info(ctx, "database time:", v.String())
}
这个检查有两个好处:一是能确认 GoFrame 读到的就是当前配置;二是能把连接、认证、SQL 执行拆开看。连 SELECT NOW() 都过不了,就没必要继续查业务表。
如果你的项目使用多数据源,也可以把 default 换成指定分组:
go
v, err := g.DB("default").GetValue(ctx, "SELECT NOW()")
6. 一张排查表,按顺序看
| 现象 | 优先检查 | 常见原因 |
|---|---|---|
| 本地正常,服务器连接失败 | host、port、账号授权 | 配置文件没读到、数据库只监听本地、用户 host 不匹配 |
| 时间字段扫描报错 | DSN 的 `parseTime` | MySQL 时间字段没有转成 `time.Time` |
| 中文或 emoji 写入异常 | `charset`、库表字符集 | DSN 和数据库字符集不一致 |
| 时间差 8 小时 | DSN `loc`、数据库时区 | 本地和服务器时区不同 |
| PostgreSQL 表不存在 | `search_path`、schema | 表在 public,查询走了别的 schema |
| 初始化脚本执行失败 | SQL 方言和报错行号 | MySQL/PostgreSQL 脚本混用或表已存在 |
7. 最后再回到项目配置
等数据库能独立连通,再回到项目配置。GoFrame 后台项目一般要确认这几项:
yaml
database:
default:
link: "mysql:user:password@tcp(127.0.0.1:3306)/app_db?charset=utf8mb4&parseTime=true&loc=Local"
debug: true
maxIdle: 10
maxOpen: 100
maxLifetime: "30s"
开发环境可以打开 SQL debug,生产环境要按日志量决定是否关闭。连接池参数不要照抄,先用默认值跑通,再结合数据库连接数上限调整。
如果需要找一个真实的 GoFrame + Vue3 后台项目对照目录和初始化脚本,可以看 XYGo Admin:
重点不是照搬配置,而是看它把 manifest/config、mysql_install.sql、pgsql_install.sql 这些东西分开维护。数据库问题排查到最后,往往不是某一行代码神秘失效,而是配置、实例、脚本三者没有对齐。
我的建议是保存一份最小排查命令清单。下次遇到"本地正常,服务器不行",先跑 SELECT NOW()、字符集、时区、search_path,再看业务代码。这样能少绕很多弯路。