本文 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。 |
使用 sqlx、gorm 或自行抽象層,避免硬編碼。 |
忘記 Close() |
連線池未釋放會導致資源耗盡。 | 一定 在 defer db.Close() 或 defer rows.Close()。 |
| 未檢查錯誤 | Exec、Query 失敗時直接忽略,會產生不可預期行為。 |
每一次 DB 呼叫後 立即 檢查 err,並適當記錄。 |
| 大量資料一次查詢 | 大量結果一次載入記憶體會 OOM。 | 使用 rows.Next() 逐筆處理,或加上 LIMIT/OFFSET。 |
| 時間格式不一致 | 不同 DB 對 datetime 的解析方式不同。 |
使用 time.Time 並在 DSN 加入 parseTime=true(MySQL)或 sslmode=disable(PostgreSQL)等參數。 |
最佳實踐
- 使用預備語句(
Prepare/Exec)避免 SQL Injection。 - 統一錯誤處理:可自訂
DBError包裝底層錯誤,方便上層判斷。 - 設定連線池參數:
SetMaxOpenConns、SetMaxIdleConns、SetConnMaxLifetime,根據實際負載調整。 - 使用 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 後端服務。祝開發順利!