在使用golang连接MySQL的时候,经常会用到parseTime
和loc
。
这两个参数的作用是什么呢?
先说下不带这两个参数存在的问题:
- 使用
go-sql-driver/mysql
来连接MySQL数据库,时区默认是UTC的,读取或 写入的时间都是UTC时间。而本地是东八区时间。 - 获取到的MySQL中的日期时间字段时,是string类型。如果转到
time.Time
类型,需要在代码中用time.Parse
进行转化。
下面看下这两个参数。
1.parseTime
和loc
parseTime
Type: bool
Valid Values: true, false
Default: false
如果配置了parseTime=true
,MySQL中的DATE
、DATETIME
等时间类型字段将自动转换为golang中的time.Time
类型。 类似的0000-00-00 00:00:00
,会被转为time.Time
的零值。
否则,如果没有配置或配置了parseTime=false
, 只会转为 []byte
/ string
。
loc
Type: string
Valid Values: <escaped name>
Default: UTC
设置转换为 time.Time
类型时, 使用的时区信息 (当设置parseTime=true)。
默认值 UTC
,表示解析为UTC时间。
一般设置为Local
,表示使用当地时间。
这个设置只表示解析为time.Time
类型时,使用的配置。并不改变MySQL的time zone时区信息time_zone setting。
如果需要在连接参数DSN中做这个配置,可参考time_zone system variable。
对于MySQL上的时区配置信息,可执行SQL查看:
mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | CST |
| time_zone | SYSTEM |
+------------------+--------+
2 rows in set (0.05 sec)
CST
除了表示中国的时区,也表示美国的时区。
time_zone
表示用于初始化每个连接的时区信息。默认SYSTEM
,表示值同system_time_zone
。
2.举例
新建表
CREATE TABLE `process_steps` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`process_uid` varchar(128) NOT NULL,
`audit_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`message` text CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`id`),
KEY `process_uid_idx` (`process_uid`),
KEY `audit_at_idx` (`audit_at`)
) ENGINE=InnoDB DEFAULT CHARSET=ascii;
2.1 不带参数
不能自动解析到 time.Time
类型,需要单独调用time.Parse()
。
2.1.1 使用NOW() 作为时间参数
下面例子中,使用SQL的NOW()作为时间参数写入数据库。接着有两次读出数据操作。
第一次直接读取到结构体中,第二次读取到buffer中,并调用time.Parse()
将时间转到time.Time
,使用的时间格式是2006-01-02 15:04:05
。
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
)
var DB *sql.DB
var dataBase = "root:Aa123456@tcp(127.0.0.1:3306)/test?timeout=2s&readTimeout=6s&interpolateParams=true"
func init() {
var err error
DB, err = sql.Open("mysql", dataBase)
if err != nil {
log.Println("open db fail:", err)
}
DB.SetMaxOpenConns(20)
DB.SetMaxIdleConns(15)
err = DB.Ping()
if err != nil {
log.Fatalln("ping db fail:", err)
}
}
func main() {
insertWithSQLTime()
loadWithStruct()
loadWithBuffer()
}
func insertWithSQLTime() {
sql := "insert into process_steps(process_uid, audit_at, message) values (?, NOW(), ?)"
fmt.Println("sql:", sql)
uid := "AA-CC-HH"
msg := "abcd"
_, err := DB.Exec(sql, uid, msg)
if err != nil {
fmt.Println("exec failed:", err, ", sql:", sql)
return
}
}
type ProcessStep struct {
Id int64
ProcessUID string
AuditAt string
Message string
}
const DateTimeFormat = "2006-01-02 15:04:05"
func loadWithStruct() {
sql := `select
id, process_uid, audit_at, message
from
process_steps
where
process_uid=?`
uid := "AA-CC-HH"
rows, err := DB.Query(sql, uid)
if err != nil {
fmt.Println("query failed:", err)
return
}
defer rows.Close()
for rows.Next(){
step := &ProcessStep{}
if err := rows.Scan(&step.Id, &step.ProcessUID, &step.AuditAt, &step.Message); err != nil {
fmt.Println("Scan failed:", err)
}
fmt.Println("step:", step)
}
}
func loadWithBuffer() {
sqlStr := `select
id, process_uid, audit_at, message
from
process_steps
where
process_uid=?`
uid := "AA-CC-HH"
rows, err := DB.Query(sqlStr, uid)
if err != nil {
fmt.Println("query failed:", err)
return
}
defer rows.Close()
for rows.Next(){
buff := make([]interface{}, 4)
data := make([]sql.NullString, 4)
for i, _ := range buff {
buff[i] = &data[i]
}
if err := rows.Scan(buff...); err != nil {
fmt.Println("Scan failed:", err)
}
fmt.Println("data from db:", data)
fmt.Println("audit_at:", GetTimeWithParseTime(data[2].String))
}
}
func GetTimeWithParseTime(key string) time.Time {
if t, err := time.Parse(DateTimeFormat, key); err == nil {
return t
}
return time.Time{}
}
output
sql: insert into process_steps(process_uid, audit_at, message) values (?, NOW(), ?)
step: &{1 AA-CC-HH 2022-05-03 10:44:13 abcd}
data from db: [{1 true} {AA-CC-HH true} {2022-05-03 10:44:13 true} {abcd true}]
audit_at: 2022-05-03 10:44:13 +0000 UTC
查看db
mysql> select * from process_steps;
+----+-------------+---------------------+---------+
| id | process_uid | audit_at | message |
+----+-------------+---------------------+---------+
| 1 | AA-CC-HH | 2022-05-03 10:44:13 | abcd |
+----+-------------+---------------------+---------+
1 row in set (0.00 sec)
从日志和db中的数据看,结果基本符合预期的。
另外,如果将上面结构体中AuditAt
字段修改为time.Time
类型,会报错:
Scan failed: sql: Scan error on column index 2, name "audit_at": unsupported Scan, storing driver.Value type []uint8 into type *time.Time
也就是不能自动转换为time.Time
。
2.1.2 使用time.Time类型值作为时间参数
大部分代码同上,不同的地方是,插入记录时,使用time.Time
类型时间作为参数。
func insertWithTime() {
now := time.Now()
sql := "insert into process_steps(process_uid, audit_at, message) values (?, ?, ?)"
fmt.Println("sql:", sql)
uid := "AA-DD-HH"
msg := "abcd"
_, err := DB.Exec(sql, uid, now, msg)
if err != nil {
fmt.Println("exec failed:", err, ", sql:", sql)
return
}
}
output
sql: insert into process_steps(process_uid, audit_at, message) values (?, ?, ?)
step: &{2 AA-DD-HH 2022-05-03 03:31:59 abcd}
data from db: [{2 true} {AA-DD-HH true} {2022-05-03 03:31:59 true} {abcd true}]
audit_at: 2022-05-03 03:31:59 +0000 UTC
写入时,使用的UTC时间。
读出来的也是UTC时间。
查看db,时间不是期望的本地时间。
+----+-------------+---------------------+---------+
| id | process_uid | audit_at | message |
+----+-------------+---------------------+---------+
| 2 | AA-DD-HH | 2022-05-03 03:31:59 | abcd |
+----+-------------+---------------------+---------+
2.2 带参数
连接数据库时,带上参数loc
, parseTime
:
var dataBase = "root:Aa123456@tcp(127.0.0.1:3306)/test?timeout=2s&readTimeout=6s&interpolateParams=true&parseTime=true&loc=Local"
自动解析到 time.Time
类型,不需要单独调用time.Parse()
。
2.2.1 使用NOW() 作为时间参数
大部分代码同2.1.1,除了:
- 连接数据库的参数
- 将结构体字段
AuditAt
类型修改为time.Time
- 调用
time.Parse()
解析时间时,使用格式time.RFC3339
output:
sql: insert into process_steps(process_uid, audit_at, message) values (?, NOW(), ?)
step: &{3 AA-EE-HH 2022-05-03 11:50:06 +0800 CST abcd}
data from db: [{3 true} {AA-EE-HH true} {2022-05-03T11:50:06+08:00 true} {abcd true}]
audit_at: 2022-05-03 11:50:06 +0800 CST
查看db
+----+-------------+---------------------+---------+
| id | process_uid | audit_at | message |
+----+-------------+---------------------+---------+
| 3 | AA-EE-HH | 2022-05-03 11:50:06 | abcd |
+----+-------------+---------------------+---------+
读取时,时间可以自动解析,时区信息是东八区。
2.2.2 使用time.Time类型值作为时间参数
大部分代码同2.1.2,除了:
- 连接数据库的参数
- 将结构体字段
AuditAt
类型修改为time.Time
- 调用
time.Parse()
解析时间时,使用格式time.RFC3339
output
sql: insert into process_steps(process_uid, audit_at, message) values (?, ?, ?)
step: &{4 AA-FF-HH 2022-05-03 12:04:06 +0800 CST abcd}
data from db: [{4 true} {AA-FF-HH true} {2022-05-03T12:04:06+08:00 true} {abcd true}]
audit_at: 2022-05-03 12:04:06 +0800 CST
查看
+----+-------------+---------------------+---------+
| id | process_uid | audit_at | message |
+----+-------------+---------------------+---------+
| 4 | AA-FF-HH | 2022-05-03 12:04:06 | abcd |
+----+-------------+---------------------+---------+
写入时,使用的东八区时间。
读取时,时间可以自动解析,时区信息是东八区。
3.小结
是否带时间解析参数 | 是否自动解析到time.Time类型 | 使用NOW() 作为时间参数 | 使用time.Time类型值作为时间参数 |
---|---|---|---|
否 | 不能自动解析到 time.Time类型,需要单独调用time.Parse() | 写入当前时间:2022-05-03 10:44:13 db:2022-05-03 10:44:13 读取:2022-05-03 10:44:13 time format:2006-01-02 15:04:05 解析到time.Time 2022-05-03 10:44:13 +0000 UTC | 写入当前时间: 2022-05-03 11:31:59 db: 2022-05-03 03:31:59 读取:2022-05-03 03:31:59 time format:2006-01-02 15:04:05 解析到time.Time:2022-05-03 03:31:59 +0000 UTC(写入的是UTC时间) |
是 | 自动解析到 time.Time类型,不需要单独调用time.Parse() | 写入当前时间: 2022-05-03 11:50:06 db: 2022-05-03 11:50:06 读取:2022-05-03T11:50:06+08:00 time format:2006-01-02T11:50:06+08:00 解析到time.Time:2022-03-16T11:50:06 +0800 CST | 写入当前时间:2022-05-03 12:04:06 db:2022-05-03 12:04:06 读取:2022-05-03T12:04:06+08:00 time format:2006-01-02T15:04:05+08:00 解析到time.Time:2022-05-03 12:04:06 +0800 CST (写入的是东八区时间) |