最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

go編程中g(shù)o-sql-driver的離奇bug解決記錄分析

 更新時間:2023年05月18日 09:05:19   作者:SOFAStack  
這篇文章主要為大家介紹了go編程中g(shù)o-sql-driver的離奇bug解決記錄分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

對于 Go CURD Boy 來說,相信 github.com/go-sql-driver/mysql 這個庫都不會陌生?;旧?Go 的 CURD 都離不開這個特別重要的庫。我們在開發(fā) Seata-go 時也使用了這個庫。

不過最近在使用 go-sql-driver/mysql 查詢 MySQL 的時候,就出現(xiàn)一個很有意思的 bug, 覺得有必要分享出來,以防止后來者再次踩坑。

PART. 1 問題詳述

為了說明問題,這里不詳述 Seata-go 的相關(guān)代碼,用一個單獨的 demo 把問題詳細描述清楚。

1.1 環(huán)境準備

在一個 MySQL 實例上準備如下環(huán)境:

CREATE TABLE `Test1` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE
CURRENT_TIMESTAMP,  
-PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

從這個 SQL 語句中可以看出來, create_time 是 timestamp 類型,這里要特別留意 timestamp 這個類型。

現(xiàn)在插入一條數(shù)據(jù),然后查看剛插入的數(shù)據(jù)的值。

insert?into?Test1?values?(1,?'2022-01-01?00:00:00')

查看下 MySQL 當前的時區(qū)。請記好相關(guān)值,草蛇灰線,伏筆于此。

show?VARIABLES?like?'%time_zone%';

查詢結(jié)果:

接下來使用 MySQL unix_timestamp 查看 create_time 的時間戳:

SELECT?unix_timestamp(create_time)?from?Test1?where?id?=?1;

查詢結(jié)果:

1.2 測試程序

有如下 demo 程序,示例使用 go-sql-driver 讀取 create_time 的值:

package main
import ( 
"database/sql" 
"fmt" 
"time"
    _ "github.com/go-sql-driver/mysql"
    )
func main() {
var user = "user" 
var pwd = "password" 
var dbName = "dbname"
  dsn := fmt.Sprintf("%s:%s@tcp(localhost:3306)/%stimeout=100s&parseTime=true&interpolateParams=true", user, pwd, dbName)  
  db, err := sql.Open("mysql", dsn)
  if err != nil { 
  panic(err) 
  } 
  defer db.Close()
  rows, err := db.Query("select create_time 
  from Test1 limit 1") 
  if err != nil {  
  panic(err)  
  }  
  for rows.Next() {  
  t := time.Time{}   
  rows.Scan(&t)  
  fmt.Println(t)   
  fmt.Println(t.Unix())  }}

我們運行個程序會輸出下面的結(jié)果:

2022-01-01 00:00:00 +0000 UTC1640995200

1.3 問題詳述

發(fā)現(xiàn)問題所在了嗎?有圖如下,把結(jié)果放在一塊,可以詳細說明問題。

圖中紅色箭頭指向的兩個結(jié)果,用 go-sql-driver 讀取的結(jié)果和在 MySQL 中用 unix_timestamp 獲取的結(jié)果明顯是不一樣的。

PART. 2 問題探案

1.3 小節(jié)中最后示圖可以看出,數(shù)據(jù)庫中 create_time  的值 2022-01-01 00:00:00 是東八區(qū)的時間,也就是北京時間,這個時間對應的時間戳就是 1640966400 。但是 go-sql-driver 示例程序讀出來的卻是 1640995200 , 這是什么值?這是 0 時區(qū)的 2022-01-01 00:00:00。

對問題的直白描述就是:MySQL 的 create_time 是 2022-01-01 00:00:00 +008 ,而讀取到的是 2022-01-01 00:00:00 +000 ,他倆壓根就不是一個值。

基本能看出來 bug 是如何發(fā)生的了。那就需要剖析下 go-sql-driver 源碼,追查問題的根源。

2.1 go-sq-driver 源碼分析

這里就不粘貼 "github.com/go-sql-driver/mysql" 的詳細源碼了,只貼關(guān)鍵的路徑。

Debug 的時候詳細關(guān)注調(diào)用路徑中紅色的兩個方塊的內(nèi)存中的值。

// https://github.com/go-sql-driver/mysql/blob/master/packets.go#L788-
L798
func (rows *textRows) readRow(dest []driver.Value) error {
  // ... 
  // Parse time field  
  switch rows.rs.columns[i].fieldType
  { 
  case fieldTypeTimestamp, 
  fieldTypeDateTime,  
  fieldTypeDate,  
  fieldTypeNewDate:   
  if dest[i], err = parseDateTime(dest[i].([]byte), mc.cfg.Loc);
  err != nil {      return err    }  }}
func parseDateTime(b []byte, loc *time.Location) (time.Time, error) {  const base = "0000-00-00 00:00:00.000000"  switch len(b) {  case 10, 19, 21, 22, 23, 24, 25, 26: // up to "YYYY-MM-DD HH:MM:SS.MMMMMM"
    year, err := parseByteYear(b)
    month := time.Month(m)
    day, err := parseByte2Digits(b[8], b[9])
    hour, err := parseByte2Digits(b[11], b[12])
    min, err := parseByte2Digits(b[14], b[15])
    sec, err := parseByte2Digits(b[17], b[18])
    // https://github.com/go-sql-driver/mysql/blob/master/utils.go#L166-L168    if len(b) == 19 {      return time.Date(year, month, day, hour, min, sec, 0, loc), nil    }  }}

從這里基本上就能明白,go-sql-driver 把數(shù)據(jù)庫讀出來的 create_time timestamp 值當做一個字符串,然后按照 MySQL timestamp 的標準格式 "0000-00-00 00:00:00.000000" 去解析,分別得到 year, month, day, hour, min, sec。最后依賴傳入 time.Location 值,調(diào)用 Go 系統(tǒng)庫 time.Date() 再去生成對應的值。

這里表面看起來沒有問題,其實這里嚴重依賴了傳入的 time.Location。這個 time.Location 是如何得到的呢?進一步閱讀源碼,可以明顯的看出來,是通過解析傳入的 DSN 的 Loc 獲取。

其中關(guān)鍵代碼是:https://github.com/go-sql-driver/mysql/blob/master/dsn.go#L467-L474 。

如果傳入的 DSN 串不帶 Loc 時,Loc 就是默認的 UTC 時區(qū)。

2.2 抽絲剝繭

回頭看開頭的程序,初始化 go-sql-driver 的 DSN 是 user:password@tcp(localhost:3306)/dbname?timeout=100s&parseTime=true&interpolateParams=true,該 DSN 里面并不包含 Loc 信息,go-sql-driver 用使用了默認的 UTC 時區(qū)。然后解析從 MySQL 中獲取的 timestamp 字段了,也就用默認的 UTC 時區(qū)去生成 Date,結(jié)果也就錯了。

因此,問題的主要原因是:go-sql-driver 并沒有按照數(shù)據(jù)庫的時區(qū)去解析 timestamp 字段,而且依賴了開發(fā)者生成的 DSN 傳入的 Loc。當開發(fā)者傳入的 Loc 和數(shù)據(jù)庫的 time_Zone 不匹配的時候,所有的 timestamp 字段都會解析錯誤。

有些人可能有疑問,如果 go-sql-driver 為什么不直接使用 MySQL 的時區(qū)去解析 timestamp 呢?

我們已經(jīng)提了一個 issue,商討更好的解決方案:https://github.com/go-sql-dri...。

PART. 3 最后結(jié)論

在 MySQL 中讀寫 timestamp 類型數(shù)據(jù)時,有如下注意事項:

  • 默認約定:寫入 MySQL 時間時,把當前時區(qū)的時間轉(zhuǎn)換為 UTC + 00:00(世界標準時區(qū))的值,讀取后在前端展示時再次進行轉(zhuǎn)換;
  • 如果不愿意使用默認約定,在現(xiàn)階段使用 go-sql-driver 的時候,一定要特別注意,需要在 DSN 字符串加上 "loc=true&time_zone=*" , 和數(shù)據(jù)的時區(qū)保持一致,不然的話就會導致 timestamp 字段解析錯誤。

| 參考文檔 |

《The date, datetime, and timestamp Types》 

https://dev.mysql.com/doc/refman/8.0/en/datetime.html

《MySQL 的 timestamp 會存在時區(qū)問題?》

http://m.fzitv.net/article/255355.htm

《Feature request: Fetch connection time_zone automatically》

https://github.com/go-sql-driver/mysql/issues/1379

以上就是go編程中g(shù)o-sql-driver的離奇bug解決記錄分析的詳細內(nèi)容,更多關(guān)于go-sql-driver bug的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go?mod?tidy命令的使用

    go?mod?tidy命令的使用

    gomodtidy命令是Go語言中用于管理項目依賴的工具,主要功能包括移除未使用的依賴項、添加缺失的依賴項以及更新go.sum文件以確保依賴項的正確校驗,感興趣的可以了解一下
    2024-11-11
  • 詳解go+gin 入門指南

    詳解go+gin 入門指南

    本文主要介紹了go語言環(huán)境的安裝和gin框架的簡單使用,就像java的hello world一樣,簡單入門,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2026-02-02
  • Go語言中的字符串拼接方法詳情

    Go語言中的字符串拼接方法詳情

    本文介紹Go語言中的string類型、strings包和bytes.Buffer類型,介紹幾種字符串拼接方法的相關(guān)資料,需要的朋友可以參考一下,希望對你有所幫助
    2021-10-10
  • Go并發(fā):使用sync.WaitGroup實現(xiàn)協(xié)程同步方式

    Go并發(fā):使用sync.WaitGroup實現(xiàn)協(xié)程同步方式

    這篇文章主要介紹了Go并發(fā):使用sync.WaitGroup實現(xiàn)協(xié)程同步方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • 一文教你Golang如何正確關(guān)閉通道

    一文教你Golang如何正確關(guān)閉通道

    Go在通道這一塊,沒有內(nèi)置函數(shù)判斷通道是否已經(jīng)關(guān)閉,也沒有可以直接獲取當前通道數(shù)量的方法,因此如果對通道進行了錯誤的使用,將會直接引發(fā)系統(tǒng)?panic,這是一件很危險的事情,下面我們就來學習一下如何正確關(guān)閉通道吧
    2023-10-10
  • golang結(jié)構(gòu)體slice排序方式

    golang結(jié)構(gòu)體slice排序方式

    go語言的slice()不僅可以對int類型的數(shù)組進行排序,還可以對struct類型的數(shù)組進行排序,排序函數(shù)包括Slice()、SliceStable()和SliceIsSorted(),結(jié)構(gòu)體可以定義為更復雜的類型
    2025-12-12
  • Go語言中的HTTP客戶端從標準庫到第三方庫

    Go語言中的HTTP客戶端從標準庫到第三方庫

    本文介紹了Go語言中的HTTP客戶端開發(fā),涵蓋標準庫和第三方庫使用、最佳實踐、高級功能、HTTP/2支持、安全考慮、性能優(yōu)化等內(nèi)容,通過本文,讀者可以構(gòu)建高效、可靠的HTTP客戶端應用
    2026-05-05
  • Go語言中的Struct結(jié)構(gòu)體

    Go語言中的Struct結(jié)構(gòu)體

    這篇文章介紹了Go語言中的Struct結(jié)構(gòu)體,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • 詳解Go語言中獲取文件路徑的不同方法與應用場景

    詳解Go語言中獲取文件路徑的不同方法與應用場景

    在使用?Go?開發(fā)項目時,估計有不少人遇到過無法正確處理文件路徑的問題,本文將嘗試從簡單到復雜,詳細介紹?Go?中獲取路徑的不同方法及應用場景,希望對大家有所幫助
    2024-02-02
  • Go數(shù)組的具體使用

    Go數(shù)組的具體使用

    Go語言中的數(shù)組是一種固定長度的數(shù)據(jù)結(jié)構(gòu),它包含一組按順序排列的元素,每個元素都具有相同的類型,本文主要介紹了Go數(shù)組的具體使用,包括聲明數(shù)組、初始化數(shù)組、訪問數(shù)組元素等,感興趣的可以了解下
    2023-11-11

最新評論

包头市| 太康县| 乌鲁木齐县| 舟山市| 沂南县| 东兰县| 五大连池市| 日照市| 遂溪县| 苍山县| 乌兰县| 海林市| 奉化市| 江北区| 夏河县| 平凉市| 卓尼县| 镇巴县| 北碚区| 太和县| 彝良县| 白玉县| 桂阳县| 长宁区| 凉山| 鲁甸县| 西平县| 桓台县| 行唐县| 甘肃省| 太仆寺旗| 轮台县| 桑日县| 黄浦区| 神木县| 崇阳县| 科技| 驻马店市| 神木县| 凤翔县| 宜兴市|