在使用golang连接MySQL的时候,经常会用到parseTimeloc
这两个参数的作用是什么呢?

先说下不带这两个参数存在的问题:

  • 使用go-sql-driver/mysql来连接MySQL数据库,时区默认是UTC的,读取或 写入的时间都是UTC时间。而本地是东八区时间。
  • 获取到的MySQL中的日期时间字段时,是string类型。如果转到time.Time类型,需要在代码中用time.Parse进行转化。

下面看下这两个参数。

1.parseTimeloc

parseTime

Type:           bool
Valid Values:   true, false
Default:        false

如果配置了parseTime=true,MySQL中的DATEDATETIME等时间类型字段将自动转换为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 (写入的是东八区时间)

4.参考

go-sql-driver/mysql

time.LoadLocation

mysql 里 CST 时区的坑

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐