Go读取MySQL Date类型的避坑指南
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一不小心就踩坑</a></li><li><a href="#_label1">问题出在哪里?</a></li><li><a href="#_label2">总结</a></li></ul></div><p class="maodian"><a name="_label0"></a></p><h2>一不小心就踩坑</h2><p>先举一个实际的:</p>
<p>我们先创建一个表,并插入一行数据。注意表中两个字段一个是<code>DATETIME</code>类型,一个是<code>DATE</code>类型的</p>
<div class="jb51code"><pre class="brush:sql;">CREATE TABLE `t_test` (
`id` int NOT NULL AUTO_INCREMENT,
`f_one` datetime DEFAULT NULL,
`f_two` date DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `test`.`t_test` (`f_one`, `f_two`) VALUES ('2025-11-29 10:25:05', '2025-11-29');
</pre></div>
<p>当我们查询数据可以看到和插入的数据一致</p>
<ul><li><code>f_one datetime</code>展示为<code>2025-11-29 10:25:05</code></li><li><code>f_two date</code>展示为<code>2025-11-29</code></li></ul>
<div class="jb51code"><pre class="brush:sql;">mysql> select * from t_test where id = 1;
+----+---------------------+------------+
| id | f_one | f_two |
+----+---------------------+------------+
|1 | 2025-11-29 10:25:05 | 2025-11-29 |
+----+---------------------+------------+
1 row in set (0.003 sec)
</pre></div>
<p>现在使用Go来读取上面的数据</p>
<div class="jb51code"><pre class="brush:go;">package main
import (
"testing"
"github.com/stretchr/testify/assert"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Test struct {
ID int `gorm:"column:id;primaryKey"`
FOne string `gorm:"column:f_one"` // 注意这里我们定义成了string类型
FTwo string `gorm:"column:f_two"` // 注意这里我们定义成了string类型
}
func (t Test) TableName() string {
return "t_test"
}
func TestRead(t *testing.T) {
// 数据库连接配置
dsn := "root:root@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn))
assert.Nil(t, err)
var result Test
err = db.Where("id = ?", 1).Find(&result).Error
assert.Nil(t, err)
assert.Equal(t, "2025-11-29 10:25:05", result.FOne)
assert.Equal(t, "2025-11-29", result.FTwo)
}
</pre></div>
<p>可以在这里暂停思考下,上面的单元测试是否会通过。下面是实际执行的结果。</p>
<div class="jb51code"><pre class="brush:go;">Error: Not equal:
expected: "2025-11-29 10:25:05"
actual: "2025-11-29T10:25:05+08:00"
Error: Not equal:
expected: "2025-11-29"
actual: "2025-11-29T00:00:00+08:00"
</pre></div>
<p>结果非常Amazing啊</p>
<p><code>f_one</code>我们期望的是<code>2025-11-29 10:25:05</code>,实际Go读取到是 <code>2025-11-29T10:25:05+08:00</code>,额外添加了T分隔符和时区信息</p>
<p><code>f_two</code>我们期望的是<code>2025-11-29</code>, 实际Go读取到是 <code>2025-11-29T00:00:00+08:00</code>,不仅额外添加了T分隔符和时区信息,还添加了时分秒</p>
<p>你看,如果你对Go读取到字符串按MySQL时间格式处理,可能一不注意就踩坑里了。</p>
<p class="maodian"><a name="_label1"></a></p><h2>问题出在哪里?</h2>
<p>你可能已经注意到了,Go结构体的定义使用了<code>string</code>类型,但不知道你有没有注意到数据库连接中的另外一个参数<code>parseTime=True</code>。</p>
<p><code>parseTime</code>是什么?<code>parseTime</code>是 Go 的 MySQL 驱动 (<code>github.com/go-sql-driver/mysql</code>) 中的一个连接参数,控制是否将 MySQL 时间类型自动转换为 Go 的 <code>time.Time</code>对象:</p>
<ul><li><code>parseTime=false</code> 禁用自动转换(默认值)</li><li><code>parseTime=true</code> 启用自动转换</li></ul>
<p>当Go的定义为<code>string</code>类型时,问题就出在自动转换上</p>
<p><code>parseTime=false</code>时, 转化方式为<code>DATETIME/DATE(MySQL)</code>-><code>string(Go)</code> ,不会出现上面的问题,Go中读取到的字符串和MySQL的格式一致:<code>2025-11-29 10:25:05</code>和<code>2025-11-29</code></p>
<p><code>parseTime=true</code>时,Go的MySQL驱动首先尝试将 MySQL的 <code>DATETIME/DATE(MySQL)</code>解析为 Go 的 <code>time.Time</code>对象,当发现目标字段是 <code>string</code>类型时,驱动会自动调用 <code>time.Time</code>的 <code>String()</code>方法进行转换,<code>time.String()</code>按照RFC3339格式输出,与MySQL中展示的格式不一致,就形成了上文的效果。也就是说</p>
<div class="jb51code"><pre class="brush:go;"># 开启parseTime=true且Go类型定义为string时
`DATETIME(MySQL)`
|
v
`time.Time`(golang)
|
| ->这里的`time.time`到`string`这一步出现了转化差异
v
`string(golang)`
</pre></div>
<p class="maodian"><a name="_label2"></a></p><h2>总结</h2>
<p>下面总结不同<code>parseTime</code>和Go类型定义的情况下,Go程序读取到的值。</p>
<table><thead><tr><th>Go字段类型定义</th><th>parseTime=false</th><th>parseTime=true</th></tr></thead><tbody><tr><td><code>string</code></td><td><code>YYYY-MM-DD[ HH:MM:SS[.fraction]]</code><br />即:<br /><code>DATE</code>-><code>YYYY-MM-DD</code><br /><code>DATETIME</code>-><code>YYYY-MM-DD HH:MM:SS</code><br /><code>DATETIME(3)</code>-><code>YYYY-MM-DD HH:MM:SS.sss</code><br /><code>DATETIME(6)</code>-><code>YYYY-MM-DD HH:MM:SS.ssssss</code><br />零值-><code>0000-00-00[ 00:00:00]</code><br /><br />此时Go中时间字符串的格式和MySQL默认格式一致</td><td>RFC3339<br /><code>YYYY-MM-DDTHH:MM:SS±HH:MM</code><br /><br />此时Go中时间字符串的格式和MySQL默认格式不一致!!</td></tr><tr><td><code>time.Time</code></td><td>报错 (类型不兼容)</td><td>Go<code>time.Time</code>实例<br />如果零值也是零值<code>time.Time</code>实例</td></tr></tbody></table>
<p>由此也引出了比较合理的使用姿势</p>
<ul><li>如果启用了<code>parseTime=true</code>,我建议你在Go中定义成<code>time.Time</code>类型</li></ul>
<div class="jb51code"><pre class="brush:go;">type Test struct {
ID int `gorm:"column:id;primaryKey"`
FOne time.Time `gorm:"column:f_one"`
FTwo time.Time `gorm:"column:f_two"`
}
</pre></div>
<ul><li>如果没有启用,即<code>parseTime=false</code>,在Go只能定义成<code>string</code></li></ul>
<div class="jb51code"><pre class="brush:go;">type Test struct {
ID int `gorm:"column:id;primaryKey"`
FOne string `gorm:"column:f_one"`
FTwo string `gorm:"column:f_two"`
}
</pre></div>
頁:
[1]