本文 AI 產出,尚未審核

Golang 資料庫操作:MySQL、PostgreSQL 與 SQLite 完整指南

簡介

在現代的後端開發中,資料庫是系統的核心,而 Go 語言因其高效能與簡潔的語法,已成為許多服務的首選。無論是大型企業級的 MySQL、功能強大的 PostgreSQL,或是輕量級的 SQLite,都能在 Go 中得到良好的支援。掌握這三種資料庫的基本操作,能讓你在不同的專案需求間快速切換,提升開發效率與系統彈性。

本篇文章將從 連線設定、CRUD 操作、交易管理 等核心概念切入,提供可直接套用的程式碼範例,並說明常見的陷阱與最佳實踐,幫助初學者到中階開發者在實務上快速上手。

核心概念

1. 使用 database/sql 與驅動程式

Go 標準庫的 database/sql 提供了統一的介面,只要匯入對應的驅動程式,即可操作不同的資料庫。以下列出三個常用驅動:

資料庫 驅動套件 匯入方式
MySQL github.com/go-sql-driver/mysql _ "github.com/go-sql-driver/mysql"
PostgreSQL github.com/lib/pq _ "github.com/lib/pq"
SQLite github.com/mattn/go-sqlite3 _ "github.com/mattn/go-sqlite3"

小技巧:在 go.mod 中加入相應的套件後,使用 _ 匿名匯入即可讓 sql 套件自動註冊驅動。

2. 建立資料庫連線

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"      // MySQL
    _ "github.com/lib/pq"                   // PostgreSQL
    _ "github.com/mattn/go-sqlite3"         // SQLite
)

func main() {
    // MySQL DSN: username:password@tcp(host:port)/dbname?charset=utf8mb4&parseTime=True
    mysqlDSN := "root:secret@tcp(127.0.0.1:3306)/testdb?parseTime=True"
    dbMySQL, err := sql.Open("mysql", mysqlDSN)
    if err != nil { panic(err) }
    defer dbMySQL.Close()

    // PostgreSQL DSN: postgres://username:password@host:port/dbname?sslmode=disable
    pgDSN := "postgres://postgres:secret@localhost:5432/testdb?sslmode=disable"
    dbPG, err := sql.Open("postgres", pgDSN)
    if err != nil { panic(err) }
    defer dbPG.Close()

    // SQLite DSN: file path (":memory:" for in‑memory DB)
    sqliteDSN := "file:sample.db?cache=shared&_fk=1"
    dbSQLite, err := sql.Open("sqlite3", sqliteDSN)
    if err != nil { panic(err) }
    defer dbSQLite.Close()

    fmt.Println("All databases connected successfully!")
}
  • sql.Open 僅建立 連線池,真正的連線會在第一次執行查詢時建立。
  • 為避免資源浪費,務必在程式結束或不再使用時呼叫 Close()

3. CRUD 基本範例

3.1 建立表格(以 SQLite 為例)

func createTable(db *sql.DB) error {
    stmt := `
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        age INTEGER,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );`
    _, err := db.Exec(stmt)
    return err
}

3.2 插入資料(使用預備語句)

func insertUser(db *sql.DB, name string, age int) (int64, error) {
    // 使用 ? 佔位符,MySQL、SQLite 使用 ?, PostgreSQL 使用 $1、$2…
    stmt, err := db.Prepare("INSERT INTO users (name, age) VALUES (?, ?)")
    if err != nil { return 0, err }
    defer stmt.Close()

    res, err := stmt.Exec(name, age)
    if err != nil { return 0, err }
    return res.LastInsertId()
}

注意:在 PostgreSQL 中,佔位符必須改為 $1, $2,若想保持跨 DB 相容,可使用 sqlx 或自行封裝。

3.3 查詢單筆資料

type User struct {
    ID        int
    Name      string
    Age       int
    CreatedAt string
}

func getUserByID(db *sql.DB, id int) (*User, error) {
    row := db.QueryRow("SELECT id, name, age, created_at FROM users WHERE id = ?", id)
    var u User
    if err := row.Scan(&u.ID, &u.Name, &u.Age, &u.CreatedAt); err != nil {
        return nil, err
    }
    return &u, nil
}

3.4 更新資料

func updateUserAge(db *sql.DB, id, newAge int) (int64, error) {
    res, err := db.Exec("UPDATE users SET age = ? WHERE id = ?", newAge, id)
    if err != nil { return 0, err }
    return res.RowsAffected()
}

3.5 刪除資料

func deleteUser(db *sql.DB, id int) (int64, error) {
    res, err := db.Exec("DELETE FROM users WHERE id = ?", id)
    if err != nil { return 0, err }
    return res.RowsAffected()
}

4. 交易(Transaction)

在需要 原子性 的操作時,務必使用交易。以下示範一次性插入兩筆資料,若任一失敗則全部回滾:

func transferAge(db *sql.DB, fromID, toID, amount int) error {
    tx, err := db.Begin()
    if err != nil { return err }
    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p)
        }
    }()

    // 減少 fromID 的年齡
    if _, err = tx.Exec("UPDATE users SET age = age - ? WHERE id = ?", amount, fromID); err != nil {
        tx.Rollback()
        return err
    }
    // 增加 toID 的年齡
    if _, err = tx.Exec("UPDATE users SET age = age + ? WHERE id = ?", amount, toID); err != nil {
        tx.Rollback()
        return err
    }
    return tx.Commit()
}
  • defer 內的 recover 可避免因 panic 而遺漏 Rollback()
  • PostgreSQL 的交易支援更完整的隔離等級,可根據需求在 BEGIN TRANSACTION ISOLATION LEVEL ... 中指定。

常見陷阱與最佳實踐

陷阱 說明 解決方案
佔位符不一致 MySQL/SQLite 使用 ?,PostgreSQL 使用 $1$2 使用 sqlxgorm 或自行抽象層,避免硬編碼。
忘記 Close() 連線池未釋放會導致資源耗盡。 一定defer db.Close()defer rows.Close()
未檢查錯誤 ExecQuery 失敗時直接忽略,會產生不可預期行為。 每一次 DB 呼叫後 立即 檢查 err,並適當記錄。
大量資料一次查詢 大量結果一次載入記憶體會 OOM。 使用 rows.Next() 逐筆處理,或加上 LIMIT/OFFSET
時間格式不一致 不同 DB 對 datetime 的解析方式不同。 使用 time.Time 並在 DSN 加入 parseTime=true(MySQL)或 sslmode=disable(PostgreSQL)等參數。

最佳實踐

  1. 使用預備語句Prepare / Exec)避免 SQL Injection。
  2. 統一錯誤處理:可自訂 DBError 包裝底層錯誤,方便上層判斷。
  3. 設定連線池參數SetMaxOpenConnsSetMaxIdleConnsSetConnMaxLifetime,根據實際負載調整。
  4. 使用 context:在長時間查詢或交易時加入 context.WithTimeout,防止阻塞。
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
row := db.QueryRowContext(ctx, "SELECT ...")

實際應用場景

場景 推薦資料庫 為何選擇
大型電商平台 MySQL / PostgreSQL 支援高併發、水平擴充、完整事務。
行動 App 離線儲存 SQLite 輕量、單檔、零伺服器部署,適合本機快取。
分析報表系統 PostgreSQL 內建 JSONB、全文搜尋、視圖與窗口函式,適合複雜查詢。
微服務間共享資料 MySQL(主從)或 PostgreSQL(Logical Replication) 支援多節點同步與容錯。

實務小技巧:在微服務中,常會將 資料庫抽象層 包裝成 Repository 介面,讓不同資料庫的實作只需要切換驅動即可,減少耦合。

總結

本文從 連線、CRUD、交易 三大核心概念出發,示範了 MySQL、PostgreSQL 與 SQLite 在 Go 語言中的基本操作與最佳實踐。透過統一的 database/sql 介面,我們可以在不同資料庫之間靈活切換;同時,注意佔位符差異、資源釋放與錯誤處理,才能寫出 可靠且易維護 的資料存取程式碼。希望你在實際專案中,能將這些概念快速落地,打造高效能的 Go 後端服務。祝開發順利!