Golang開(kāi)發(fā)之字符串與切片問(wèn)題踩坑記錄
背景
在項(xiàng)目中,我們使用mysql來(lái)存儲(chǔ)數(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類(lèi)型,190代表字符長(zhǎng)度,并且是唯一索引
為什么name字段設(shè)置最大字符長(zhǎng)度為190?
name是varchar類(lèi)型,并且是唯一索引。mysql規(guī)定varchar類(lèi)型為索引時(shí),最大長(zhǎng)度為767字節(jié)
name字段的編碼格式為utf8mb4_unicode_ci,一個(gè)字符最多用4個(gè)字節(jié)來(lái)表示,767 / 4 = 191.75,所以只需限制最大字符長(zhǎng)度小于191.75即可,項(xiàng)目里取190作為限制
業(yè)務(wù)代碼邏輯很簡(jiǎn)單,主要有兩步:
- 依據(jù)name查詢label表,如果查詢到了label信息,則直接返回
- 如果沒(méi)有查詢到label信息,則嘗試創(chuàng)建label信息
偽代碼:
const ( // 最大字符長(zhǎng)度 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) // 沒(méi)有找到對(duì)應(yīng)的label信息,且字符串長(zhǎng)度超過(guò)190,則切片后再次查詢 if IsRecordNotFoundError(err) && len(name) > LabelNameLengthLimit { label, err = db_reader.GetLabel(name[:LabelNameLengthLimit]) } // 還是報(bào)錯(cuò),則嘗試創(chuàng)建label信息 if err != nil { label.Name = name err = db_writer.CreateLabel(ctx, &label) // 報(bào)唯一鍵沖突錯(cuò)誤,可能是由于并發(fā)創(chuàng)建導(dǎo)致的問(wèn)題,再次兜底進(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 }
問(wèn)題
部分case,首次執(zhí)行業(yè)務(wù)代碼成功,后續(xù)執(zhí)行業(yè)務(wù)代碼一直報(bào)錯(cuò)
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)報(bào)錯(cuò)
分析
后續(xù)執(zhí)行業(yè)務(wù)代碼時(shí)
- 依據(jù)name,一直查詢不到對(duì)應(yīng)的label信息,err報(bào)錯(cuò)RecordNotFoundError
- 依據(jù)name[:LabelNameLengthLimit],一直查詢不到對(duì)應(yīng)的label信息,err報(bào)錯(cuò)RecordNotFoundError
var err error // 依據(jù)name查詢label信息 label, err = db_reader.GetLabel(name) // 沒(méi)有找到對(duì)應(yīng)的label信息,且字符串長(zhǎng)度超過(guò)190,則切片后再次查詢 if IsRecordNotFoundError(err) && len(name) > LabelNameLengthLimit { label, err = db_reader.GetLabel(name[:LabelNameLengthLimit]) }
err != nil時(shí),嘗試創(chuàng)建label信息,此時(shí)報(bào)唯一鍵沖突錯(cuò)誤
線上可能是并發(fā)創(chuàng)建導(dǎo)致的唯一鍵沖突,兜底查詢一次,此時(shí)查詢還是報(bào)錯(cuò)RecordNotFoundError
err = db_writer.CreateLabel(ctx, &label) // 報(bào)唯一鍵沖突錯(cuò)誤,可能是由于并發(fā)創(chuàng)建導(dǎo)致的問(wèn)題,再次兜底進(jìn)行查詢 if IsKeyConflict(err) { label, err = db_reader.GetLabel(ctx, name) if err != nil { return label, err } }
由于字符長(zhǎng)度超過(guò)了190,定位到主要的問(wèn)題是:依據(jù)切片后name[:LabelNameLengthLimit]查詢時(shí),沒(méi)有查詢到結(jié)果,但是依據(jù)name去創(chuàng)建label信息時(shí),報(bào)唯一鍵沖突,說(shuō)明查詢的值和實(shí)際存儲(chǔ)的值不一致
golang string底層實(shí)現(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底層其實(shí)是[]byte
- Len 代表字節(jié)切片的長(zhǎng)度,避免多次獲取字符串長(zhǎng)度時(shí),重復(fù)計(jì)算
- 內(nèi)置函數(shù)len(string),獲取的是字符串字節(jié)長(zhǎng)度
- 對(duì)字符串進(jìn)行切片labelName[:LabelNameLengthLimit],是按照字節(jié)數(shù)進(jìn)行切片
所以問(wèn)題就是:查詢和存儲(chǔ)時(shí),截取字符串的標(biāo)準(zhǔn)不一樣
- 當(dāng)字符串字符長(zhǎng)度超過(guò)190時(shí),查詢時(shí)是按照字節(jié)進(jìn)行截取
- 當(dāng)字符串字符長(zhǎng)度超過(guò)190時(shí),存儲(chǔ)時(shí)是按照字符進(jìn)行截取
測(cè)試代碼:
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é)長(zhǎng)度:", len(labelName)) fmt.Println("按照字節(jié)截取內(nèi)容:", labelName[:LabelNameLengthLimit]) fmt.Println("字符長(zhǎng)度:", 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é)長(zhǎng)度: 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?
字符長(zhǎng)度: 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切片,使用字符切片來(lái)表示字符串
- 判斷rune切片長(zhǎng)度是否超過(guò)限制,超過(guò)則依據(jù)字符長(zhǎng)度進(jìn)行切片
const ( // 最大字符長(zhǎng)度 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) // 還是報(bào)錯(cuò),則嘗試創(chuàng)建label信息 if err != nil { label.Name = name err = db_writer.CreateLabel(ctx, &label) // 報(bào)唯一鍵沖突錯(cuò)誤,可能是由于并發(fā)創(chuàng)建導(dǎo)致的問(wèn)題,再次兜底進(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開(kāi)發(fā)之字符串與切片問(wèn)題踩坑記錄的文章就介紹到這了,更多相關(guān)Go字符串切片內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Go語(yǔ)言中字符串與字節(jié)切片轉(zhuǎn)換的詳細(xì)過(guò)程
- go中實(shí)現(xiàn)字符切片和字符串互轉(zhuǎn)
- Golang切片連接成字符串的實(shí)現(xiàn)示例
- golang字符串切片去重的幾種算法
- 深度剖析Golang中的數(shù)組,字符串和切片
- go語(yǔ)言字符串的拼接和切片方法總結(jié)
- 詳解Go語(yǔ)言如何實(shí)現(xiàn)字符串切片反轉(zhuǎn)函數(shù)
- golang 字符串切片去重實(shí)例
- Go語(yǔ)言實(shí)現(xiàn)字符串切片賦值的方法小結(jié)
- Go字符串切片操作str1[:index]的使用
相關(guān)文章
Go 1.21新增的slices包中切片函數(shù)用法詳解
Go 1.21新增的 slices 包提供了很多和切片相關(guān)的函數(shù),可以用于任何類(lèi)型的切片,本文通過(guò)代碼示例為大家介紹了部分切片函數(shù)的具體用法,感興趣的小伙伴可以了解一下2023-08-08golang構(gòu)建HTTP服務(wù)的實(shí)現(xiàn)步驟
其實(shí)很多框架都是在 最簡(jiǎn)單的http服務(wù)上做擴(kuò)展的的,基本上都是遵循h(huán)ttp協(xié)議,本文主要介紹了golang構(gòu)建HTTP服務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12關(guān)于go get 下載第三方包存儲(chǔ)路徑問(wèn)題
這篇文章主要介紹了關(guān)于go get 下載第三方包存儲(chǔ)路徑問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01Go語(yǔ)言使用Request,Response處理web頁(yè)面請(qǐng)求
這篇文章主要介紹了Go語(yǔ)言使用Request,Response處理web頁(yè)面請(qǐng)求,需要的朋友可以參考下2022-04-04GoLang中panic與recover函數(shù)以及defer語(yǔ)句超詳細(xì)講解
這篇文章主要介紹了GoLang的panic、recover函數(shù),以及defer語(yǔ)句,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-01-01Go實(shí)現(xiàn)簡(jiǎn)單的數(shù)據(jù)庫(kù)表轉(zhuǎn)結(jié)構(gòu)體詳解
這篇文章主要為大家介紹了Go實(shí)現(xiàn)簡(jiǎn)單的數(shù)據(jù)庫(kù)表轉(zhuǎn)結(jié)構(gòu)體詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01