golang-database

golang-database

熱門

Go 資料庫存取的完整指南 — 參數化查詢、結構掃描、可為 NULL 的欄位、交易、隔離層級、SELECT FOR UPDATE、連線池、批次處理、Context 傳遞以及遷移工具。適用於撰寫、審查或除錯與 PostgreSQL、MariaDB、MySQL 或 SQLite 互動的 Golang 程式碼;用於資料庫測試;或關於 database/sql、sqlx 或 pgx 的問題。不產生資料庫綱要或遷移 SQL。

2261星標
150分支
更新於 2026/6/6
SKILL.md
唯讀
名稱
golang-database
描述

Go 資料庫存取的完整指南 — 參數化查詢、結構掃描、可為 NULL 的欄位、交易、隔離層級、SELECT FOR UPDATE、連線池、批次處理、Context 傳遞以及遷移工具。適用於撰寫、審查或除錯與 PostgreSQL、MariaDB、MySQL 或 SQLite 互動的 Golang 程式碼;用於資料庫測試;或關於 database/sql、sqlx 或 pgx 的問題。不產生資料庫綱要或遷移 SQL。

角色: 你是一位 Go 後端工程師,撰寫安全、明確且可觀察的資料庫程式碼。你將 SQL 視為第一級語言 — 沒有 ORM,沒有魔法 — 並且在邊界處捕獲資料完整性問題,而不是在應用程式深處。

模式:

  • 撰寫模式 — 產生新的儲存庫函式、查詢輔助工具或交易包裝器:遵循技能的順序指示;在背景啟動代理程式,在產生新程式碼前搜尋程式碼庫中現有的查詢模式和命名慣例。
  • 審查/除錯模式 — 審計或除錯現有的資料庫程式碼:使用子代理程式掃描遺失的 rows.Close()、未參數化的查詢、遺失的 Context 傳遞以及遺漏的錯誤檢查,同時並行讀取業務邏輯。

社群預設。 明確取代 samber/cc-skills-golang@golang-database 技能的團隊技能具有優先權。

Go 資料庫最佳實務

Go 的 database/sql 為資料庫存取提供了堅實的基礎。在其上使用 sqlxpgx 以獲得更好的操作體驗 — 永遠不要使用 ORM。

使用 sqlx 或 pgx 時,請參考該函式庫的官方文件和程式碼範例以取得最新的 API 簽名。

最佳實務摘要

  1. 使用 sqlx 或 pgx,不要用 ORM — ORM 隱藏 SQL,產生不可預測的查詢,並使除錯更困難
  2. 查詢必須使用參數化佔位符 — 絕不要將使用者輸入串接到 SQL 字串中
  3. Context 必須傳遞給所有資料庫操作 — 使用 *Context 方法變體(QueryContextExecContextGetContext
  4. sql.ErrNoRows 必須明確處理 — 使用 errors.Is 區分「找不到」與真正的錯誤
  5. Rows 必須在迭代後關閉 — 在 QueryContext 呼叫後立即使用 defer rows.Close()
  6. 絕不要對不傳回行的陳述使用 db.QueryQuery 傳回 *Rows,必須關閉;如果忘記,連線會洩漏回池中。請使用 db.Exec
  7. 對多陳述操作使用交易 — 將相關寫入包裝在 BeginTxx/Commit
  8. 在讀取打算修改的資料時使用 SELECT ... FOR UPDATE — 防止競爭條件
  9. 設定自訂隔離層級 — 當預設的 READ COMMITTED 不足時(例如,金融操作使用可序列化)
  10. 使用指標欄位(*string*int)或 sql.NullXxx 型別處理可為 NULL 的欄位
  11. 必須設定連線池SetMaxOpenConnsSetMaxIdleConnsSetConnMaxLifetimeSetConnMaxIdleTime
  12. 使用外部工具進行遷移 — golang-migrate 或 Flyway,絕不要用手寫或 AI 產生的遷移 SQL
  13. 以合理的大小進行批次操作 — 不要逐行(太多往返),也不要一次數百萬行(鎖定和記憶體)
  14. 絕不要建立或修改資料庫綱要 — 在玩具資料上看起來正確的綱要可能在真實生產負載下產生熱點、鎖定競爭或遺失索引。綱要設計需要理解資料量、存取模式和生產限制,而 AI 並不具備這些
  15. 避免隱藏的 SQL 功能 — 不要在應用程式程式碼中依賴觸發器、檢視、具體化檢視、預存程序或列級安全性

函式庫選擇

函式庫 最適合 結構掃描 PostgreSQL 專用
database/sql 可攜性、最小依賴 手動 Scan
sqlx 多資料庫專案 StructScan
pgx PostgreSQL(快 30-50%) pgx.RowToStructByName 是(COPY、LISTEN、陣列)
GORM/ent 避免 魔法 被抽象化

為什麼不使用 ORM:

  • 不可預測的查詢產生 — 在程式碼中看不到的 N+1 問題
  • 魔法鉤子和回呼(BeforeCreate、AfterUpdate)使除錯更困難
  • 綱要遷移與應用程式程式碼耦合
  • 學習 ORM API 比學習 SQL 更難,而且抽象會洩漏

參數化查詢

// ✗ 非常糟糕 — SQL 注入漏洞
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)

// ✓ 良好 — 參數化(PostgreSQL)
var user User
err := db.GetContext(ctx, &user, "SELECT id, name, email FROM users WHERE email = $1", email)

// ✓ 良好 — 參數化(MySQL)
err := db.GetContext(ctx, &user, "SELECT id, name, email FROM users WHERE email = ?", email)

動態 IN 子句

query, args, err := sqlx.In("SELECT * FROM users WHERE id IN (?)", ids)
if err != nil {
    return fmt.Errorf("building IN clause: %w", err)
}
query = db.Rebind(query) // 根據驅動程式調整佔位符
err = db.SelectContext(ctx, &users, query, args...)

動態欄位名稱

絕不要從使用者輸入中內插欄位名稱。使用允許清單:

allowed := map[string]bool{"name": true, "email": true, "created_at": true}
if !allowed[sortCol] {
    return fmt.Errorf("invalid sort column: %s", sortCol)
}
query := fmt.Sprintf("SELECT id, name, email FROM users ORDER BY %s", sortCol)

更多注入防護模式,請參閱 samber/cc-skills-golang@golang-security 技能。

結構掃描與可為 NULL 的欄位

對 sqlx 使用 db:"column_name" 標籤,對 pgx 使用 pgx.CollectRows 搭配 pgx.RowToStructByName。使用指標欄位(*string*time.Time)處理可為 NULL 的欄位 — 它們在掃描和 JSON 序列化時都能正常運作。請參閱掃描參考以取得所有方法的範例。

錯誤處理

func GetUser(id string) (*User, error) {
    var user User

    err := db.GetContext(ctx, &user, "SELECT id, name FROM users WHERE id = $1", id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, ErrUserNotFound // 轉換為領域錯誤
        }
        return nil, fmt.Errorf("querying user %s: %w", id, err)
    }

    return &user, nil
}

或者:

func GetUser(id string) (u *User, exists bool, err error) {
    var user User

    err := db.GetContext(ctx, &user, "SELECT id, name FROM users WHERE id = $1", id)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, false, nil // 「沒有使用者」不是技術錯誤,而是領域錯誤
        }
        return nil, false, fmt.Errorf("querying user %s: %w", id, err)
    }

    return &user, true, nil
}

永遠關閉 rows

rows, err := db.QueryContext(ctx, "SELECT id, name FROM users")
if err != nil {
    return fmt.Errorf("querying users: %w", err)
}
defer rows.Close() // 防止連線洩漏

for rows.Next() {
    // ...
}
if err := rows.Err(); err != nil { // 迭代後務必檢查
    return fmt.Errorf("iterating users: %w", err)
}

常見資料庫錯誤模式

錯誤 如何偵測 動作
找不到資料列 errors.Is(err, sql.ErrNoRows) 回傳領域錯誤
唯一性約束 檢查驅動程式特定的錯誤碼 回傳衝突錯誤
連線被拒絕 db.PingContext 回傳 err != nil 快速失敗、記錄、以退避重試
序列化失敗 PostgreSQL 錯誤碼 40001 重試整個交易
Context 已取消 errors.Is(err, context.Canceled) 停止處理、傳播

Context 傳遞

始終使用 *Context 方法變體來傳遞截止時間和取消:

// ✗ 糟糕 — 沒有 context,即使客戶端中斷連線,查詢也會執行到完成
db.Query("SELECT ...")

// ✓ 良好 — 尊重 context 取消和逾時
db.QueryContext(ctx, "SELECT ...")

有關 context 模式的深入探討,請參閱 samber/cc-skills-golang@golang-context 技能。

交易、隔離層級與鎖定

有關交易模式、隔離層級、SELECT FOR UPDATE 和鎖定變體,請參閱交易

連線池

db.SetMaxOpenConns(25)              // 限制總連線數
db.SetMaxIdleConns(10)              // 保持熱連線就緒
db.SetConnMaxLifetime(5 * time.Minute)  // 回收過時連線
db.SetConnMaxIdleTime(1 * time.Minute)  // 更快關閉閒置連線

有關大小調整指南和公式,請參閱資料庫效能

遷移

使用外部遷移工具。綱要變更需要人類審查,並理解資料量、現有索引、外鍵和生產限制。

推薦工具:

  • golang-migrate — CLI + Go 函式庫,支援所有主要資料庫
  • Flyway — 基於 JVM,廣泛用於企業環境
  • Atlas — 現代、宣告式綱要管理

遷移 SQL 應由人類撰寫和審查,在原始碼控制中進行版本管理,並透過 CI/CD 管道套用。

避免隱藏的 SQL 功能

不要在應用程式程式碼中依賴觸發器、檢視、具體化檢視、預存程序或列級安全性 — 它們會產生不可見的副作用,使除錯變得不可能。將 SQL 保持明確且可見於 Go 中,以便進行測試和版本控制。

綱要建立

此技能不涵蓋綱要建立。 AI 產生的綱要通常有微妙的錯誤 — 遺失索引、不正確的欄位型別、錯誤的正規化或遺失約束。綱要設計需要理解資料量、存取模式、查詢輪廓和業務限制。請使用專用的資料庫工具和人工審查。

深入探討

  • 交易 — 交易邊界、隔離層級、死結預防、SELECT FOR UPDATE
  • 測試資料庫程式碼 — Mock 連線、使用容器的整合測試、測試資料、綱要設定/拆除
  • 資料庫效能 — 連線池大小調整、批次處理、索引策略、查詢最佳化
  • 結構掃描 — 結構標籤、可為 NULL 的欄位處理、JSON 序列化模式

交叉參考

  • → 請參閱 samber/cc-skills-golang@golang-security 技能以了解 SQL 注入防護模式
  • → 請參閱 samber/cc-skills-golang@golang-context 技能以了解資料庫操作的 context 傳遞
  • → 請參閱 samber/cc-skills-golang@golang-error-handling 技能以了解資料庫錯誤包裝模式
  • → 請參閱 samber/cc-skills-golang@golang-testing 技能以了解資料庫整合測試模式

參考資料