Golang開發(fā)之字符串與切片問題踩坑記錄
背景
在項目中,我們使用mysql來存儲數(shù)據(jù)信息,其中l(wèi)abel表記錄了標(biāo)簽相關(guān)的信息。表結(jié)構(gòu)如下:
CREATE TABLE `label` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `name` varchar(190) COLLATE utf8mb4_unicode_ci DEFAULT 'label name', PRIMARY KEY (`id`), UNIQUE KEY `uniq_n` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=7050965 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='label'
其中name字段為varchar類型,190代表字符長度,并且是唯一索引
為什么name字段設(shè)置最大字符長度為190?
name是varchar類型,并且是唯一索引。mysql規(guī)定varchar類型為索引時,最大長度為767字節(jié)
name字段的編碼格式為utf8mb4_unicode_ci,一個字符最多用4個字節(jié)來表示,767 / 4 = 191.75,所以只需限制最大字符長度小于191.75即可,項目里取190作為限制
業(yè)務(wù)代碼邏輯很簡單,主要有兩步:
- 依據(jù)name查詢label表,如果查詢到了label信息,則直接返回
- 如果沒有查詢到label信息,則嘗試創(chuàng)建label信息
偽代碼:
const ( // 最大字符長度 LabelNameLengthLimit = 190 ) func GetOrCreateLabel(ctx context.Context, name string) (label dao.Label, err error) { var err error // 依據(jù)name查詢label信息 label, err = db_reader.GetLabel(name) // 沒有找到對應(yīng)的label信息,且字符串長度超過190,則切片后再次查詢 if IsRecordNotFoundError(err) && len(name) > LabelNameLengthLimit { label, err = db_reader.GetLabel(name[:LabelNameLengthLimit]) } // 還是報錯,則嘗試創(chuàng)建label信息 if err != nil { label.Name = name err = db_writer.CreateLabel(ctx, &label) // 報唯一鍵沖突錯誤,可能是由于并發(fā)創(chuàng)建導(dǎo)致的問題,再次兜底進(jìn)行查詢 if IsKeyConflict(err) { label, err = db_reader.GetLabel(ctx, name) if err != nil { return label, err } } if err != nil { return label, err } } return label, nil }
問題
部分case,首次執(zhí)行業(yè)務(wù)代碼成功,后續(xù)執(zhí)行業(yè)務(wù)代碼一直報錯
case1:
labelName := "? 2015 Charanga Sí o Ké - Chema Mu?oz, Perfecto Artola, Pablo Guerrero, Antonio Flores, Los Gipsy King, Chambao, Adele, La Pegatina, El Chaval de la Peca, Los Manolos ? 2015 Charanga Sí o Ké - Sones de Rumba"
執(zhí)行:
- 首次執(zhí)行業(yè)務(wù)代碼,執(zhí)行成功
- 后續(xù)執(zhí)行業(yè)務(wù)代碼,22行穩(wěn)定復(fù)現(xiàn)報錯
分析
后續(xù)執(zhí)行業(yè)務(wù)代碼時
- 依據(jù)name,一直查詢不到對應(yīng)的label信息,err報錯RecordNotFoundError
- 依據(jù)name[:LabelNameLengthLimit],一直查詢不到對應(yīng)的label信息,err報錯RecordNotFoundError
var err error // 依據(jù)name查詢label信息 label, err = db_reader.GetLabel(name) // 沒有找到對應(yīng)的label信息,且字符串長度超過190,則切片后再次查詢 if IsRecordNotFoundError(err) && len(name) > LabelNameLengthLimit { label, err = db_reader.GetLabel(name[:LabelNameLengthLimit]) }
err != nil時,嘗試創(chuàng)建label信息,此時報唯一鍵沖突錯誤
線上可能是并發(fā)創(chuàng)建導(dǎo)致的唯一鍵沖突,兜底查詢一次,此時查詢還是報錯RecordNotFoundError
err = db_writer.CreateLabel(ctx, &label) // 報唯一鍵沖突錯誤,可能是由于并發(fā)創(chuàng)建導(dǎo)致的問題,再次兜底進(jìn)行查詢 if IsKeyConflict(err) { label, err = db_reader.GetLabel(ctx, name) if err != nil { return label, err } }
由于字符長度超過了190,定位到主要的問題是:依據(jù)切片后name[:LabelNameLengthLimit]查詢時,沒有查詢到結(jié)果,但是依據(jù)name去創(chuàng)建label信息時,報唯一鍵沖突,說明查詢的值和實際存儲的值不一致
golang string底層實現(xiàn)go/src/reflect/value.go
// StringHeader is the runtime representation of a string. // It cannot be used safely or portably and its representation may // change in a later release. // Moreover, the Data field is not sufficient to guarantee the data // it references will not be garbage collected, so programs must keep // a separate, correctly typed pointer to the underlying data. type StringHeader struct { Data uintptr Len int }
其中:
- Data 指向底層的[]byte的首地址,string底層其實是[]byte
- Len 代表字節(jié)切片的長度,避免多次獲取字符串長度時,重復(fù)計算
- 內(nèi)置函數(shù)len(string),獲取的是字符串字節(jié)長度
- 對字符串進(jìn)行切片labelName[:LabelNameLengthLimit],是按照字節(jié)數(shù)進(jìn)行切片
所以問題就是:查詢和存儲時,截取字符串的標(biāo)準(zhǔn)不一樣
- 當(dāng)字符串字符長度超過190時,查詢時是按照字節(jié)進(jìn)行截取
- 當(dāng)字符串字符長度超過190時,存儲時是按照字符進(jìn)行截取
測試代碼:
labelName := "? 2015 Charanga Sí o Ké - Chema Mu?oz, Perfecto Artola, Pablo Guerrero, Antonio Flores, Los Gipsy King, Chambao, Adele, La Pegatina, El Chaval de la Peca, Los Manolos ? 2015 Charanga Sí o Ké - Sones de Rumba" fmt.Println("原字符串內(nèi)容:", labelName) fmt.Println("字節(jié)長度:", len(labelName)) fmt.Println("按照字節(jié)截取內(nèi)容:", labelName[:LabelNameLengthLimit]) fmt.Println("字符長度:", len([]rune(labelName))) fmt.Println("按照字符截取內(nèi)容:", string([]rune(labelName)[:LabelNameLengthLimit]))
輸出:
原字符串內(nèi)容: © 2015 Charanga Sí o Ké - Chema Muñoz, Perfecto Artola, Pablo Guerrero, Antonio Flores, Los Gipsy King, Chambao, Adele, La Pegatina, El Chaval de la Peca, Los Manolos © 2015 Charanga Sí o Ké - Sones de Rumba
字節(jié)長度: 214
按照字節(jié)截取內(nèi)容: © 2015 Charanga Sí o Ké - Chema Muñoz, Perfecto Artola, Pablo Guerrero, Antonio Flores, Los Gipsy King, Chambao, Adele, La Pegatina, El Chaval de la Peca, Los Manolos © 2015 Charanga S?
字符長度: 207
按照字符截取內(nèi)容: © 2015 Charanga Sí o Ké - Chema Muñoz, Perfecto Artola, Pablo Guerrero, Antonio Flores, Los Gipsy King, Chambao, Adele, La Pegatina, El Chaval de la Peca, Los Manolos © 2015 Charanga Sí o Ké
解決
解決方案:
- 先把string轉(zhuǎn)rune切片,使用字符切片來表示字符串
- 判斷rune切片長度是否超過限制,超過則依據(jù)字符長度進(jìn)行切片
const ( // 最大字符長度 LabelNameLengthLimit = 190 ) func GetOrCreateLabel(ctx context.Context, name string) (label dao.Label, err error) { if rs := []rune(name); len(rs) > LabelNameLengthLimit { name = string(rs[:LabelNameLengthLimit]) } var err error // 依據(jù)name查詢label信息 label, err = db_reader.GetLabel(name) // 還是報錯,則嘗試創(chuàng)建label信息 if err != nil { label.Name = name err = db_writer.CreateLabel(ctx, &label) // 報唯一鍵沖突錯誤,可能是由于并發(fā)創(chuàng)建導(dǎo)致的問題,再次兜底進(jìn)行查詢 if IsKeyConflict(err) { label, err = db_reader.GetLabel(ctx, name) if err != nil { return label, err } } if err != nil { return label, err } } return label, nil }
到此這篇關(guān)于Golang開發(fā)之字符串與切片問題踩坑記錄的文章就介紹到這了,更多相關(guān)Go字符串切片內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go 1.21新增的slices包中切片函數(shù)用法詳解
Go 1.21新增的 slices 包提供了很多和切片相關(guān)的函數(shù),可以用于任何類型的切片,本文通過代碼示例為大家介紹了部分切片函數(shù)的具體用法,感興趣的小伙伴可以了解一下2023-08-08golang構(gòu)建HTTP服務(wù)的實現(xiàn)步驟
其實很多框架都是在 最簡單的http服務(wù)上做擴(kuò)展的的,基本上都是遵循h(huán)ttp協(xié)議,本文主要介紹了golang構(gòu)建HTTP服務(wù),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12Go語言使用Request,Response處理web頁面請求
這篇文章主要介紹了Go語言使用Request,Response處理web頁面請求,需要的朋友可以參考下2022-04-04GoLang中panic與recover函數(shù)以及defer語句超詳細(xì)講解
這篇文章主要介紹了GoLang的panic、recover函數(shù),以及defer語句,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01Go實現(xiàn)簡單的數(shù)據(jù)庫表轉(zhuǎn)結(jié)構(gòu)體詳解
這篇文章主要為大家介紹了Go實現(xiàn)簡單的數(shù)據(jù)庫表轉(zhuǎn)結(jié)構(gòu)體詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01