近期有同事反馈,测试环境的数据库存储的datetime与北京时间有差异,看到这个问题我的第一反应可能是时区配置有问题。首先检查了一下mysql服务器的时区配置,配置的确实是北京时间,所以就排除了mysql服务器的问题,接下来再检查了一下业务服务器的时区配置,发现配置的UTC时间,由此可以初步确定是业务服务器的问题。通常遇到这个问题最快的解决方案是将服务器的时区改为北京时间,然而仔细思考一番,假如真的有服务器时区配置不一致的问题,难道就只能通过事后发现的修复方案,没有预防方案?
我们使用golang作为后端语言,数据库操作层面使用了gorm框架,查阅了gorm相关技术文档,发现gorm是支持时区配置的,那么这个gorm时区配置是否真的可以作为一个预防方案?实践出真知,接下来将通过一些样例代码来验证这个问题。
准备
(1) 将本地环境的时区调为UTC
(2) 使用mysql数据库,建立一张测试表1
2
3CREATE TABLE test(
d datetime NOT NULL
);
样例代码:入库时间使用本地时区
1 | conStr := "root:123456@tcp(192.168.3.93:33061)/zxd?charset=utf8mb4&parseTime=true&loc=Local" |
gorm配置的时区为使用本地时区,通过查询数据库表可以观察得到,生成的时间为本地当前时间。
样例代码:使用配置时区
1 | conStr := "root:123456@tcp(192.168.3.93:33061)/zxd?charset=utf8mb4&parseTime=true&loc=Asia%2fShanghai" |
注意 loc=Asia%2fShanghai
,gorm配置链接字符串要求对Loc做UrlEncode处理,这里配置成固定的北京时间,通过查询数据库可以验证入库的时间正确性。
翻看gorm的源码,可以看到,拼接sql的时候,gorm使用配置的时区对time.Time类型的变量做了一次转换,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17case time.Time:
paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00
var a [64]byte
var b = a[:0]
if v.IsZero() {
b = append(b, "0000-00-00"...)
} else {
b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
}
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(b)),
)
paramValues = append(paramValues, b...)
这里需要注意一个细节,就是配置的时区只对time.Time类型有效,假如我们使用了time.Now().Format()进行格式化当前时间,则会使用本地时间,因为作为一个sting占位符,gorm并不会做任何处理。