golang时间和mysql时间表示

2023-03-01 16:22:17 浏览数 (3)

在聊时间这个话题之前我们先了解两个概念:墙上时钟和单调时钟

墙上时钟:也称为墙上时间。大多是1970年1月1日(UTC)以来的秒数和毫秒数。墙上时间可以和NTP(Network Time Protocal,网络时间协议)同步,但是如果本地时钟远远快于NTP服务器,则强制重置之后会跳到先前某个时间点。

单调时钟:机器大多有自己的石英晶体振荡器,并将其作为计时器。单调时钟的绝对值没有任何意义,根据操作系统和语言的不同,单调时钟可能在程序开始时设为0、或在计算机启动后设为0等等。但是通过比较同一台计算机上两次单调时钟的差,可以获得相对准确的时间间隔。

在go 1.9之前,记录比较简单,就是1-1-1 00:00:00 到现在的整数s和ns数,以及时区数据。也就是墙上时钟

代码语言:javascript复制
type Time struct {
  // sec gives the number of seconds elapsed since
  // January 1, year 1 00:00:00 UTC.
  sec int64
  // nsec specifies a non-negative nanosecond
  // offset within the second named by Seconds.
  // It must be in the range [0, 999999999].
  nsec int32
  // loc specifies the Location that should be used to
  // determine the minute, hour, month, day, and year
  // that correspond to this Time.
  // The nil location means UTC.
  // All UTC times are represented with loc==nil, never loc==&utcLoc.
  loc *Location
}

在1.9之后记录了墙上时钟和单调时钟,wall和ext共同记录了时间,但是分为两种情况:

代码语言:javascript复制
type Time struct {
   wall uint64
   ext  int64
   loc *Location
}

一种是没有记录单调时钟(比如是通过字符串解析得到的时间),另一种是记录了单调时钟(比如通过Now)。wall的第一位是一个标记位:

  1. 如果为1,则表示记录了单调时钟。则wall的2-34(闭区间)位记录了从1885-1-1到现在的秒数,最后30位记录了纳秒数。而ext记录了从程序开始运行到现在经过的单调时钟数。单位nanoseconds
  2. 如果为0,则表示没有记录单调时钟。则wall的2-34(闭区间)位全部为0(最后30位记录了纳秒数)。而ext记录了从1-1-1 00:00:00到现在经过的秒数。

单调时钟的计算逻辑如下

代码语言:javascript复制
 var startNano = 0
 
 func init(){
     startNano = runtimeNano()
 }
 
 runtimeNano() - startNano

Time其实是实现了String函数的,因此可以print,打印出的时间格式为“年月日时分秒.小数秒” 精度为ns;后面还带着"m=±<value>",表示单调时钟的s数表示,小数点后精度到ns

代码语言:javascript复制
func (t Time) String() string {

// String returns the time formatted using the format string
//  "2006-01-02 15:04:05.999999999 -0700 MST"
//
// If the time has a monotonic clock reading, the returned string
// includes a final field "m=±<value>", where value is the monotonic
// clock reading formatted as a decimal number of seconds.

    m1, m2 := m2/1e9, m29
    m0, m1 := m1/1e9, m19


     if m0 != 0 {
      buf = appendInt(buf, int(m0), 0)
      wid = 9
    }
    buf = appendInt(buf, int(m1), wid)
    buf = append(buf, '.')
    buf = appendInt(buf, int(m2), 9)
 }

了解完golang的时间格式表示,我们过来看下mysql的时间格式表示:

  1. MySQL DATETIME存储包含日期和时间的值。从DATETIME列查询数据时,MySQL会以以下格式显示DATETIME值:YYYY-MM-DD HH:MM:SS。默认情况下,DATETIME的值范围为1000-01-01 00:00:00至9999-12-31 23:59:59。DATETIME使用5个字节进行存储。另外,DATETIME值可以包括格式为YYYY-MM-DD HH:MM:SS [.fraction]例如:2017-12-20 10:01:00.999999的尾数有小数秒。 当包含小数秒精度时,DATETIME值需要更多存储2017-12-20 10:01:00.999999需要8个字节,2015-12-20 10:01:00需要5个字节,3个字节为.999999,而2017-12-20 10:01:00.9只需要6个字节,小数秒精度为1字节。在MySQL 5.6.4之前,DATETIME值需要8字节存储而不是5个字节。
  2. TIMESTAMP需要4个字节,而DATETIME需要5个字节。 TIMESTAMP和DATETIME都需要额外的字节,用于分数秒精度。 TIMESTAMP范围从1970-01-01 00:00:01 UTC到2038-01-19 03:14:07 UTC。 如果要存储超过2038的时间值,则应使用DATETIME而不是TIMESTAMP。

总结下,也就是说常用的5.7版本,时间戳只能存到2038年,精度是秒,但是只需要4个字节,DATETIME存储的时间长度为5到8个字节,精度是微秒。

那么问题来了,当我们用golang驱动写mysql和从mysql查数据的时候,精度是什么样子的呢?显然写数据的时候会丢失精度从ns圆整到us

代码语言:javascript复制
  result, err := db.Exec("insert into time_test(`ctime`) values(?)", time.Now())

然后查询会得到

代码语言:javascript复制
'2023-02-21 22:55:39.980742  0800 CST m= 0.005420710'

元整发生在什么时候呢?在github.com/go-sql-driver/mysql 1.5.0版本和以前会在驱动里将时间元整到ms,但是1.6.0版本不再元整

代码语言:javascript复制
https://github.com/go-sql-driver/mysql/commit/fe2230a8b20cee1e48f7b75a9b363def5f950ba0

就导致了一个有趣的现象,在mysql的各个版本中,因为mysql在处理时间参数的时候做了精度的元整,如果在datetime字段上加了索引,即使传了精度为ns的时间,也会走索引。但是对于marindb,如果传入的时间是ns精度,刚好把mysql驱动由1.5.0升级到了1.6.0会导致索引失效。

1 人点赞