背景
由于我司业务特殊,程序最终部署在客户现场,数据库作为关键一环也由客户确定。这就导致我们的Golang
程序连的数据库不确定,各种非国产和国产数据库都可能遇到。所以在数据库初始化时,使用了gorm
自带的AutoMigrate
函数,该函数可以自动创建表、字段列、缺失的外键、约束和索引。
现象
程序无法正常运行,报错空指针
Gorm相关代码
由于PolarDB-X
号称100%兼容Mysql
,所以在Gorm
使用时,直接以mysql
驱动形式使用。
连接到数据库
var db *gorm.DB
...
switch config.Config.DBDriver {
case constant.DriverMysql:
//mysql驱动
dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
config.Config.Mysql.UserName,
config.Config.Mysql.Password,
config.Config.Mysql.Host,
config.Config.Mysql.Port,
config.Config.Mysql.DBName,
)
db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
//Logger: newLogger, //日志
DisableForeignKeyConstraintWhenMigrating: true, //外键
PrepareStmt: true, //预编译缓存
})
case constant.DriverPostgresql:
//postgresql驱动
...
自动建表
自动建库和后续创建表数据代码省略,这里只展示自动建表代码:
func registerTables(db *gorm.DB) {
if config.Config.DBDriver == constant.DriverMysql {
err := db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(
entity.User{},
entity.Order{},
)
if err != nil {
log.WithFields(log.Fields{"err": err.Error()}).Panic("注册数据库表失败")
}
return
}
if config.Config.DBDriver == constant.DriverPostgresql{
...
}
...
}
详细用法见:
分析
通过报错可以看到在Gorm-AutoMigrate
操作时报错了空指针。而程序连接Mysql
数据库时一切正常,可知自动建表时的db
变量是有值的,那么在PolarDB
上的异常只可能跟数据库的差异有关系。可PolarDB
实在不熟悉,那就只好 遇事不决,先问AI。先把问题和报错告诉DeepSeek
看看它怎么说:
DeepSeek
给了5种可能性让排查,然后一翻操作下来,发现仍然没有解决问题。切换其他大模型也是如此。
没办法,只好看Gorm
源码,先看报错地方
通过排查发现,m.DB.Raw("SELECT DATABASE()").Row()
查出的数据为nil
,因此给&name
值时报错了无效内存地址,而m.DB
没什么问题。
结果排查半天仍未找出问题,然后看官方文档 gorm 看到举例连接数据库时配置都比较简单,于是简化我的连接配置。最终发现当配置了PrepareStmt: true
时,PolarDB
就会报错。
PreparedStmt
:执行任何 SQL 时都会创建一个 prepared statement 并将其缓存,以提高后续的效率。
但是具体为什么咱未可知。
总结
Gorm
因其功能强大,多数据库的支持和使用简单,成为最流行的Go ORM
库。而国产数据库大多使用Mysql
和Postgresql
进行二次开发,虽然号称高度兼容,实际使用时还是有细微差别。如果使用Gorm-AutoMigrate
功能。不仅要尽量简化配置,定义数据库模型时也尽量不要指定字段的类型和大小,以免切换数据库造成代码无法正常运行。
评论区