Golang database/sql 包深度解析(一)

database/sql 是 Go 語言標準庫中用于與 SQL(或類 SQL)數據庫交互的核心包,提供了一套輕量級、通用的接口,使得開發者可以用統一的方式操作各種不同的數據庫,而無需關心底層數據庫驅動的具體實現。

核心設計理念

database/sql 包本身并不包含任何特定數據庫的驅動程序,只是定義了一系列接口,而具體的數據庫驅動則需要實現這些接口。這種設計的好處在于:

  • 通用性: 開發者可以編寫與特定數據庫無關的數據訪問代碼。
  • 可移植性: 只需更換導入的數據庫驅動和連接字符串,即可輕松切換數據庫。
  • 專注性: database/sql 包專注于提供統一的數據庫操作抽象,而將底層細節交由第三方驅動實現。

在使用時,通常會像下面這樣導入 database/sql 包和一個匿名的數據庫驅動包:

import ("database/sql"_ "github.com/go-sql-driver/mysql" // 匿名導入,僅執行其init()函數
)

匿名導入(使用下劃線 _)的目的是執行驅動包的 init() 函數,該函數會將驅動注冊到 database/sql 中。之后就可以通過 database/sql 提供的函數來使用這個驅動了。

核心類型

database/sql 包主要由以下幾個核心類型組成:

類型描述
sql.DB表示一個數據庫句柄,代表一個維護了零到多個底層連接的連接池。它是并發安全的,應該被視為一個長生命周期的對象,通常在應用啟動時創建,并在整個應用生命周期內共享。
sql.Tx表示一個數據庫事務。在事務中執行的所有操作要么全部成功(Commit),要么全部失敗(Rollback)。
sql.Stmt表示一個預編譯的 SQL 語句。預編譯可以防止 SQL 注入,并且在重復執行相同語句時能提升性能。
sql.Rows表示一個查詢返回的多行結果集。使用 Next() 方法遍歷每一行,并用 Scan() 方法讀取行中的數據。
sql.Row表示一個查詢返回的單行結果。通常用于期望最多返回一行結果的查詢。
sql.Result表示 Exec 方法(用于 INSERT, UPDATE, DELETE 等操作)的執行結果,可以獲取最后插入的 ID 和受影響的行數。
sql.Null* 類型sql.NullString, sql.NullInt64, sql.NullBool 等,用于處理可能為 NULL 的數據庫列。每個類型都包含一個 Valid 字段(布爾型)來表示值是否為 NULL,以及一個 Value 字段來存儲實際的值。

基本操作

1. 連接數據庫

使用 sql.Open() 函數來創建一個 sql.DB 對象。這個函數需要兩個參數:驅動名稱和數據源名稱(Data Source Name, DSN)。

db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
if err != nil {log.Fatal(err)
}
defer db.Close()

注意: sql.Open() 并不會立即建立與數據庫的連接,也不會驗證連接參數的有效性,只是準備好數據庫抽象對象。要驗證連接是否有效,可以使用 db.Ping() 方法。

err = db.Ping()
if err != nil {log.Fatal("數據庫連接失敗: ", err)
}

sql.DB 對象內部管理著一個連接池,開發者無需手動管理連接。可以通過以下方法配置連接池:

  • db.SetMaxOpenConns(n): 設置連接池中的最大打開連接數。
  • db.SetMaxIdleConns(n): 設置連接池中的最大空閑連接數。
  • db.SetConnMaxLifetime(d): 設置連接可被復用的最大時間。

2. 執行查詢

查詢多行

使用 db.Query()db.QueryContext() 方法執行返回多行結果的 SELECT 查詢。

rows, err := db.Query("SELECT id, name FROM users WHERE age > ?", 30)
if err != nil {log.Fatal(err)
}
defer rows.Close()type User struct {ID   int64Name string
}var users []User
for rows.Next() {var u Userif err := rows.Scan(&u.ID, &u.Name); err != nil {log.Fatal(err)}users = append(users, u)
}
if err := rows.Err(); err != nil {log.Fatal(err)
}

關鍵點:

  • 參數化查詢: 使用 ? (或其他數據庫驅動支持的占位符,如 $1 for PostgreSQL) 作為參數占位符,并將實際參數傳遞給 Query 方法,可以有效防止 SQL 注入攻擊。
  • 遍歷結果: 使用 for rows.Next() 循環遍歷結果集。
  • 讀取數據: 在循環體內,使用 rows.Scan() 將當前行的數據讀入到變量中。
  • 錯誤檢查: 在 rows.Next() 循環結束后,務必調用 rows.Err() 來檢查遍歷過程中是否發生了錯誤。
  • 釋放資源: 使用 defer rows.Close() 來確保結果集被關閉,從而釋放底層的數據庫連接。

查詢單行

使用 db.QueryRow()db.QueryRowContext() 方法執行最多返回一行的查詢。

var name string
err := db.QueryRow("SELECT name FROM users WHERE id = ?", 1).Scan(&name)
if err != nil {if err == sql.ErrNoRows {// 查無此行,是正常情況fmt.Println("未找到記錄")} else {// 發生了其他錯誤log.Fatal(err)}
}

關鍵點:

  • QueryRow 總是返回一個 *sql.Row 對象(即使沒有找到行)。
  • 錯誤(包括 sql.ErrNoRows)會在調用 Scan() 方法時返回。因此,需要檢查 Scan() 返回的錯誤。

3. 執行非查詢操作

對于 INSERT, UPDATE, DELETE 等不返回行的 SQL 語句,使用 db.Exec()db.ExecContext() 方法。

result, err := db.Exec("UPDATE users SET name = ? WHERE id = ?", "New Name", 1)
if err != nil {log.Fatal(err)
}rowsAffected, err := result.RowsAffected()
if err != nil {log.Fatal(err)
}
fmt.Printf("受影響的行數: %d\n", rowsAffected)lastInsertId, err := result.LastInsertId()
if err != nil {// 注意:并非所有數據庫驅動都支持此功能log.Fatal(err)
}
fmt.Printf("最后插入的ID: %d\n", lastInsertId)

高級技巧

1. 預編譯語句 (Prepared Statements)

當需要重復執行相同的 SQL 語句時,使用預編譯語句可以獲得更好的性能,并能提供額外的安全保障。

stmt, err := db.Prepare("INSERT INTO users(name, age) VALUES(?, ?)")
if err != nil {log.Fatal(err)
}
defer stmt.Close()_, err = stmt.Exec("Alice", 28)
if err != nil {log.Fatal(err)
}_, err = stmt.Exec("Bob", 32)
if err != nil {log.Fatal(err)
}

2. 事務處理

事務用于將一組操作作為一個原子單元來執行。使用 db.Begin() 開始一個事務。

tx, err := db.Begin()
if err != nil {log.Fatal(err)
}// 在事務中執行操作
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {tx.Rollback() // 出錯時回滾log.Fatal(err)
}_, err = tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
if err != nil {tx.Rollback() // 出錯時回滾log.Fatal(err)
}// 提交事務
err = tx.Commit()
if err != nil {log.Fatal(err)
}

關鍵點:

  • tx 對象上調用 Exec, Query, QueryRow 等方法來執行事務內的操作。
  • 如果任何一步操作失敗,調用 tx.Rollback() 來撤銷所有已執行的操作。
  • 所有操作成功后,調用 tx.Commit() 來持久化更改。
  • 推薦使用 defer 語句來處理回滾,以確保在函數返回前事務能夠被正確處理:
tx, err := db.Begin()
if err != nil {// ...
}
defer tx.Rollback() // 如果Commit成功,Rollback會返回sql.ErrTxDone,可以忽略
// ... 事務操作
if err = tx.Commit(); err != nil {// ...
}

最佳實踐

  • 不要在短函數中打開和關閉數據庫: sql.DB 被設計為長生命周期的對象。頻繁地 OpenClose 會影響性能。
  • 總是檢查錯誤: database/sql 中的許多操作都會返回錯誤,務必對每一個錯誤進行檢查。
  • 使用參數化查詢: 杜絕拼接字符串來構建 SQL 查詢,以防止 SQL 注入。
  • 正確關閉 Rows: 確保 sql.Rows 對象在使用完畢后被關閉,以釋放連接。
  • 處理 NULL: 使用 sql.Null* 類型或在 Scan 時使用指針類型來處理可能為 NULL 的數據庫列。
  • 利用 Context: 對于可能耗時較長的數據庫操作,使用帶有 Context 的方法(如 QueryContext),以便在需要時能夠取消操作或設置超時。

小結

database/sql 包為 Go 語言提供了一個強大而靈活的與數據庫交互的工具。通過理解其設計和核心組件,并遵循最佳實踐,開發者可以編寫出健壯、高效且可維護的數據訪問代碼。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/93398.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/93398.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/93398.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

文章自然潤色 API 數據接口

文章自然潤色 API 數據接口 ai / 文本處理 基于 AI 的文章潤色 專有模型 / 智能糾錯。 1. 產品功能 基于自有專業模型進行 AI 智能潤色對原始內容進行智能糾錯高效的文本潤色性能全接口支持 HTTPS(TLS v1.0 / v1.1 / v1.2 / v1.3);全面兼容…

【狀壓DP】3276. 選擇矩陣中單元格的最大得分|2403

本文涉及知識點 C動態規劃 3276. 選擇矩陣中單元格的最大得分 給你一個由正整數構成的二維矩陣 grid。 你需要從矩陣中選擇 一個或多個 單元格,選中的單元格應滿足以下條件: 所選單元格中的任意兩個單元格都不會處于矩陣的 同一行。 所選單元格的值 互…

IDEA 清除 ctrl+shift+r 全局搜索記錄

定位文件:在Windows系統中,文件通常位于C:Users/用戶名/AppData/Roaming/JetBrains/IntelliJIdea(idea版本)/workspace目錄下,文件名為一小串隨機字符;在Mac系統中,文件位于/Users/用戶名/Library/Application /Suppor…

解鎖AI大模型:Prompt工程全面解析

解鎖AI大模型&#xff1a;Prompt工程全面解析 本文較長&#xff0c;建議點贊收藏&#xff0c;以免遺失。更多AI大模型開發 學習視頻/籽料/面試題 都在這>>Github<< 從新手到高手&#xff0c;Prompt 工程究竟是什么&#xff1f; 在當今數字化時代&#xff0c;AI …

HTTP0.9/1.0/1.1/2.0

在HTTP0.9中&#xff0c;只有GET方法&#xff0c;沒有請求頭headers&#xff0c;沒有狀態碼&#xff0c;只能用于傳輸HTML文件。到了HTTP1.0(1996)&#xff0c;HTTP1.0傳輸請求頭&#xff0c;有狀態碼&#xff0c;并且新增了POST和HEAD方法。HTTP1.0中&#xff0c;使用短連接&a…

gitee 流水線+docker-compose部署 nodejs服務+mysql+redis

文章中的方法是自己琢磨出來的&#xff0c;或許有更優解&#xff0c;共同學習&#xff0c;共同進步&#xff01; docker-compose.yml 文件配置&#xff1a; 說明&#xff1a;【配置中有個別字段冗余&#xff0c;但不影響使用】該文件推薦放在nodejs項目的根目錄中&#xff0c…

【算法】模擬專題

什么是模擬&#xff1f; 是一種通過模仿現實世界或問題場景的運行過程來求解問題的算法思想。它不依賴復雜的數學推導或邏輯優化&#xff0c;而是按照問題的實際規則、步驟或流程&#xff0c;一步步地 “復現” 過程&#xff0c;最終得到結果。 使用場景&#xff1a;當問題的邏…

【FreeRTOS】刨根問底6: 應該如何防止任務棧溢出?

【加關注&#xff0c;不迷路】一、棧溢出&#xff1a;程序世界的“越界洪水”就象一個裝水的玻璃杯&#xff08;棧空間&#xff09;&#xff0c;每次調用函數就像向水杯中倒水&#xff08;壓入保護需要恢復的數據&#xff09;。當函數嵌套調用過深&#xff08;如遞歸失控&#…

牛客周賽 Round 105

A.小苯的xor構造題目描述小紅喜歡整數 k&#xff0c;他想讓小苯構造兩個不相等的非負整數&#xff0c;使得兩數的異或和等于 k。請你幫幫小苯。#include <bits/stdc.h> using namespace std; using ll long long; void solve() {int k;cin>>k;cout<<0<&l…

《R for Data Science (2e)》免費中文翻譯 (第4章) --- Workflow: code style

寫在前面 本系列推文為《R for Data Science (2)》的中文翻譯版本。所有內容都通過開源免費的方式上傳至Github&#xff0c;歡迎大家參與貢獻&#xff0c;詳細信息見&#xff1a; Books-zh-cn 項目介紹&#xff1a; Books-zh-cn&#xff1a;開源免費的中文書籍社區 r4ds-zh-cn …

11-verilog的RTC驅動代碼

verilog的RTC驅動代碼 1.例化parameter SLAVE_ADDR 7h51 ; // 器件地址 parameter BIT_CTRL 1b0 ; // 字地址位控制參數(16b/8b) parameter CLK_FREQ 26d50_000_000; // i2c_dri模塊的驅動時鐘頻率(CLK_FREQ) parameter I2C_FR…

【k8s、docker】Headless Service(無頭服務)

文章目錄問題背景1、什么是Headless Service1.2 為什么 Zookeeper 使用 Headless Service&#xff1f;1.2 Headless Service 的 DNS 行為1.3 驗證示例1.4 如何創建 Headless Service&#xff1f;2. zk-0.zookeeper.default.svc.cluster.local 域名是如何創建出來的&#xff1f;…

scikit-learn/sklearn學習|套索回歸Lasso解讀

【1】引言 前序學習進程中&#xff0c;對用scikit-learn表達線性回歸進行了初步解讀。 線性回歸能夠將因變量yyy表達成由自變量xxx、線性系數矩陣www和截距bbb組成的線性函數式&#xff1a; y∑i1nwi?xibwTxby\sum_{i1}^{n}w_{i}\cdot x_{i}bw^T{x}byi1∑n?wi??xi?bwTxb實…

暴雨服務器:以定制化滿足算力需求多樣化

在數字經濟與實體經濟深度融合的浪潮下&#xff0c;互聯網行業正經歷著前所未有的技術變革。大數據分析、云計算服務、人工智能算法等技術的快速演進&#xff0c;推動著企業對于高性能計算基礎設施的需求呈現指數級增長。據IDC數據顯示&#xff0c;互聯網行業已成為全球服務器采…

JavaScript字符串詳解

創建字符串&#xff1a; 1.使用字面量(推薦)&#xff1a; 這是最常用、最直接的方式。你可以用單引號 ()、雙引號 (") 或反引號 () 把文本包起來 let singleQuote 單引號; let doubleQuote "雙引號"; let templateLiteral 反引號;2.使用String 構造函數&…

Kiro Preview 應用評測

Kiro應用評測 Kiro 是一個由亞馬遜推出的 AI 驅動的智能開發環境&#xff0c;從原型到生產全程陪伴您的開發過程。它將"靈感編程"的流暢性與規范的清晰性相結合&#xff0c;幫助您更快地構建更好的軟件。 昨天收到了Kiro的試用郵件&#xff0c;收到郵件后第一時間下載…

Flink2.0學習筆記:Flink服務器搭建與flink作業提交

一&#xff0c;下載flink:Downloads | Apache Flink,解壓后放入IDE工作目錄&#xff1a;我這里以1.17版本為例 可以看到&#xff0c;flink后期的版本中沒有提供window啟動腳本:start-cluster.bat 所以這里要通過windows自帶的wsl 系統啟動它 打開終端依次運行下列命令完成w…

MySQL鎖的分類

MySQL鎖可以按照多個維度進行分類&#xff0c;下面我用最清晰的方式為你梳理所有分類方式&#xff1a;一、按鎖的粒度分類&#xff08;最常用分類&#xff09;鎖類型作用范圍特點適用引擎示例場景表級鎖整張表開銷小、加鎖快&#xff0c;并發度低MyISAM, MEMORY數據遷移、全表統…

電腦上搭建HTTP服務器在局域網內其它客戶端無法訪問的解決方案

在電腦上開發一套HTTP服務器的程序在調試時&#xff0c;在本機內訪問正常&#xff0c;但是在本機外訪問就不正常&#xff0c;外部客戶端無法訪問或無法連接到本機的服務器的問題&#xff0c;這可能涉及網絡配置、防火墻、端口轉發或服務綁定等問題&#xff0c;在這里提供了解決…

雙指針和codetop2(最短路問題BFS)

雙指針和codetop21.雙指針1.[復寫0](https://leetcode.cn/problems/duplicate-zeros/)2.動態規劃1.[珠寶的最高價值](https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/description/)2.[解碼方法](https://leetcode.cn/problems/decode-ways/)3.[下降路徑最小和](ht…