Golang中如何正確處理數(shù)據(jù)庫(kù)操作錯(cuò)誤 以sql.ErrNoRows為例詳解

遇到 sql.errnorows 時(shí),應(yīng)根據(jù)業(yè)務(wù)場(chǎng)景判斷是“正常情況”還是“異常情況”,再進(jìn)行相應(yīng)處理。1. sql.errnorows 是 database/sql 包定義的特殊錯(cuò)誤,表示查詢未返回任何行,僅在使用 *sql.row.scan 時(shí)觸發(fā);2. 正常情況如用戶查找可能不存在的數(shù)據(jù),此時(shí)應(yīng)友好提示而非報(bào)錯(cuò);3. 異常情況如系統(tǒng)依賴數(shù)據(jù)缺失,需記錄日志或中斷流程;4. 常見(jiàn)誤區(qū)包括忽略所有錯(cuò)誤或錯(cuò)誤地用于 *sql.rows 上;5. 正確方式是對(duì)多行查詢使用 rows.next() 判斷是否有數(shù)據(jù);6. 可封裝工具函數(shù)簡(jiǎn)化單行查詢邏輯,使主流程更清晰。正確處理的關(guān)鍵在于理解語(yǔ)義并結(jié)合具體業(yè)務(wù)決定是否忽略或報(bào)錯(cuò)。

Golang中如何正確處理數(shù)據(jù)庫(kù)操作錯(cuò)誤 以sql.ErrNoRows為例詳解

golang 中處理數(shù)據(jù)庫(kù)錯(cuò)誤是一個(gè)非常關(guān)鍵的環(huán)節(jié),尤其是像 sql.ErrNoRows 這樣的錯(cuò)誤,如果不小心處理,可能會(huì)掩蓋真正的問(wèn)題或者導(dǎo)致程序邏輯出錯(cuò)。

Golang中如何正確處理數(shù)據(jù)庫(kù)操作錯(cuò)誤 以sql.ErrNoRows為例詳解

直接說(shuō)重點(diǎn):遇到 sql.ErrNoRows 時(shí),要根據(jù)業(yè)務(wù)場(chǎng)景判斷這是“正常情況”還是“異常情況”,然后做相應(yīng)的處理。

Golang中如何正確處理數(shù)據(jù)庫(kù)操作錯(cuò)誤 以sql.ErrNoRows為例詳解


sql.ErrNoRows 是什么?

sql.ErrNoRows 是 Go 標(biāo)準(zhǔn)庫(kù)中 database/sql 包定義的一個(gè)特殊錯(cuò)誤,表示查詢沒(méi)有返回任何行。它通常出現(xiàn)在你使用 QueryRow 并調(diào)用 Scan 的時(shí)候:

立即學(xué)習(xí)go語(yǔ)言免費(fèi)學(xué)習(xí)筆記(深入)”;

var name string err := db.QueryRow("SELECT name FROM users WHERE id = ?", 1).Scan(&name) if err != nil {     if err == sql.ErrNoRows {         // 沒(méi)有找到記錄     } else {         // 其他錯(cuò)誤,比如語(yǔ)法錯(cuò)誤、連接失敗等     } }

注意:只有在使用 *sql.Row 的 Scan 方法時(shí)才會(huì)返回這個(gè)錯(cuò)誤。如果是 *sql.Rows,則不會(huì)觸發(fā)這個(gè)錯(cuò)誤。

Golang中如何正確處理數(shù)據(jù)庫(kù)操作錯(cuò)誤 以sql.ErrNoRows為例詳解


如何區(qū)分正常與異常?

這一步最關(guān)鍵,因?yàn)楹芏嚅_(kāi)發(fā)者會(huì)誤以為只要出現(xiàn) sql.ErrNoRows 就是“正常的”。其實(shí)不然,這取決于你的業(yè)務(wù)邏輯。

  • 正常情況:例如根據(jù)用戶輸入的 ID 查詢信息,如果不存在,應(yīng)該友好提示而不是拋出錯(cuò)誤。
  • 異常情況:例如系統(tǒng)內(nèi)部依賴某條數(shù)據(jù)存在,但查詢不到,這時(shí)候可能需要打日志甚至中斷流程。

舉個(gè)例子:

// 正常情況示例:用戶查找一個(gè)可能不存在的內(nèi)容 user, err := getUserByID(123) if err != nil {     if err == sql.ErrNoRows {         http.NotFound(w, r)     } else {         http.Error(w, "Internal error", http.StatusInternalServerError)     } }  // 異常情況示例:定時(shí)任務(wù)依賴某個(gè)配置項(xiàng) config, err := loadConfig() if err != nil {     if err == sql.ErrNoRows {         log.Fatalf("Critical config not found")     } else {         log.Fatalf("Failed to load config: %v", err)     } }

所以,不要一看到 sql.ErrNoRows 就忽略,要看是否符合預(yù)期


常見(jiàn)錯(cuò)誤處理誤區(qū)

有些寫(xiě)法雖然能跑通,但不夠嚴(yán)謹(jǐn)或容易埋坑:

? 直接忽略所有錯(cuò)誤:

_ = db.QueryRow(...).Scan(...)

這樣不僅忽略了 sql.ErrNoRows,也忽略了其他更嚴(yán)重的錯(cuò)誤,比如字段類型不匹配、SQL 語(yǔ)法錯(cuò)誤等。

? 錯(cuò)誤地用在 *sql.Rows 上:

rows, _ := db.Query("SELECT ...") if rows == nil {     // 錯(cuò)了!rows 不會(huì)為 nil,即使沒(méi)結(jié)果也是空對(duì)象 }

db.Query 即使沒(méi)有結(jié)果也會(huì)返回非 nil 的 *sql.Rows,需要用 rows.Next() 判斷是否有數(shù)據(jù)。

? 正確方式處理多行查詢:

rows, err := db.Query("SELECT name FROM users WHERE role = ?", role) if err != nil {     log.Fatal(err) } defer rows.Close()  var names []string for rows.Next() {     var name string     if err := rows.Scan(&name); err != nil {         log.Fatal(err)     }     names = append(names, name) } if err := rows.Err(); err != nil {     log.Fatal(err) }

小技巧:封裝成工具函數(shù)簡(jiǎn)化處理

如果你經(jīng)常需要處理單行查詢并區(qū)分是否存在,可以封裝一個(gè)輔助函數(shù):

func scanRow(row *sql.Row) (string, bool, error) {     var s string     err := row.Scan(&s)     if err == sql.ErrNoRows {         return "", false, nil     }     if err != nil {         return "", false, err     }     return s, true, nil }

這樣主邏輯更清晰:

val, ok, err := scanRow(db.QueryRow("SELECT name FROM users WHERE id = ?", id)) if err != nil {     log.Fatal(err) } if !ok {     fmt.Println("Not found") } else {     fmt.Println(val) }

基本上就這些。
正確處理 sql.ErrNoRows 的關(guān)鍵是理解它的語(yǔ)義,并結(jié)合具體業(yè)務(wù)場(chǎng)景決定它是“意料之中”還是“意外情況”。別把它當(dāng)成萬(wàn)能鑰匙,該報(bào)錯(cuò)還是要報(bào)錯(cuò),該忽略才忽略。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點(diǎn)贊5 分享