在Web开发中,数据库驱动的数据访问是核心能力。合理管理连接池、规范构建查询、正确使用事务,以及在性能与可维护性间取得平衡,直接决定系统的稳定性与吞吐。本章沿用第 04.1 章的教学风格:从最小可用模型起步,逐步扩展到工程化封装与完整示例,帮助你在实际项目中快速落地数据库访问层。
1 数据库连接池管理
连接池用于复用连接、限制并发数量并控制资源占用。Go 标准库 database/sql 已内置了连接池机制,我们只需正确配置并封装即可。
1.1 连接池最小封装
go
package db
import (
"context"
"database/sql"
"fmt"
"time"
)
// Config 数据库配置(可扩展不同驱动)
type Config struct {
Driver string
DSN string
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime time.Duration
ConnMaxIdleTime time.Duration
ConnectTimeout time.Duration
}
// Open 创建 *sql.DB 并配置连接池
func Open(cfg Config) (*sql.DB, error) {
db, err := sql.Open(cfg.Driver, cfg.DSN)
if err != nil {
return nil, fmt.Errorf("打开数据库失败: %w", err)
}
db.SetMaxOpenConns(cfg.MaxOpenConns)
db.SetMaxIdleConns(cfg.MaxIdleConns)
db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
db.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)
// 连接测试
ctx, cancel := context.WithTimeout(context.Background(), cfg.ConnectTimeout)
defer cancel()
if err := db.PingContext(ctx); err != nil {
db.Close()
return nil, fmt.Errorf("数据库连接测试失败: %w", err)
}
return db, nil
}
这个最小封装具备生产可用性:统一配置、连接池参数、超时控制与启动时健康检测。
1.2 健康检查与连接池监控
go
package db
import (
"encoding/json"
"net/http"
)
// StatsHandler 输出连接池统计信息
func StatsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
stats := db.Stats()
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"open": stats.OpenConnections,
"in_use": stats.InUse,
"idle": stats.Idle,
"wait_count": stats.WaitCount,
"wait_time_ms": stats.WaitDuration.Milliseconds(),
})
}
}
在服务中注册 /db/stats 端点,结合监控系统(如 Prometheus)即可持续观察连接池压力。
2 SQL查询与轻量ORM
为了兼顾可读性与性能,推荐在"原生 SQL + 少量查询构建器"与"轻量ORM封装"之间取得平衡。
2.1 轻量查询构建器(SELECT)
go
package query
import (
"fmt"
"strings"
)
type Builder struct {
table string
cols []string
where []string
args []interface{}
orderBy string
limit int
offset int
}
func Select(table string, cols ...string) *Builder {
if len(cols) == 0 { cols = []string{"*"} }
return &Builder{table: table, cols: cols}
}
func (b *Builder) Where(cond string, arg interface{}) *Builder {
b.where = append(b.where, cond)
b.args = append(b.args, arg)
return b
}
func (b *Builder) OrderBy(ob string) *Builder { b.orderBy = ob; return b }
func (b *Builder) Limit(n int) *Builder { b.limit = n; return b }
func (b *Builder) Offset(n int) *Builder { b.offset = n; return b }
func (b *Builder) SQL() (string, []interface{}) {
sb := &strings.Builder{}
fmt.Fprintf(sb, "SELECT %s FROM %s", strings.Join(b.cols, ","), b.table)
if len(b.where) > 0 {
fmt.Fprintf(sb, " WHERE %s", strings.Join(b.where, " AND "))
}
if b.orderBy != "" { fmt.Fprintf(sb, " ORDER BY %s", b.orderBy) }
if b.limit > 0 { fmt.Fprintf(sb, " LIMIT %d", b.limit) }
if b.offset > 0 { fmt.Fprintf(sb, " OFFSET %d", b.offset) }
return sb.String(), b.args
}
这个构建器只负责"拼 SQL 与管理参数",执行仍使用 database/sql,避免过多抽象导致性能不可控。
2.2 轻量ORM模型与CRUD
go
package model
import "time"
type Product struct {
ID int64
Name string
Price float64
Category string
InStock bool
CreatedAt time.Time
UpdatedAt time.Time
}
go
package repo
import (
"context"
"database/sql"
"errors"
"time"
"yourapp/model"
)
type ProductRepo struct { db *sql.DB }
func NewProductRepo(db *sql.DB) *ProductRepo { return &ProductRepo{db: db} }
func (r *ProductRepo) GetByID(ctx context.Context, id int64) (*model.Product, error) {
row := r.db.QueryRowContext(ctx,
`SELECT id,name,price,category,in_stock,created_at,updated_at FROM products WHERE id=?`, id,
)
p := &model.Product{}
if err := row.Scan(&p.ID, &p.Name, &p.Price, &p.Category, &p.InStock, &p.CreatedAt, &p.UpdatedAt); err != nil {
if errors.Is(err, sql.ErrNoRows) { return nil, nil }
return nil, err
}
return p, nil
}
func (r *ProductRepo) Create(ctx context.Context, p *model.Product) error {
now := time.Now()
res, err := r.db.ExecContext(ctx,
`INSERT INTO products(name,price,category,in_stock,created_at,updated_at) VALUES(?,?,?,?,?,?)`,
p.Name, p.Price, p.Category, p.InStock, now, now,
)
if err != nil { return err }
id, _ := res.LastInsertId(); p.ID = id
p.CreatedAt, p.UpdatedAt = now, now
return nil
}
func (r *ProductRepo) UpdateStock(ctx context.Context, id int64, inStock bool) error {
_, err := r.db.ExecContext(ctx, `UPDATE products SET in_stock=?, updated_at=? WHERE id=?`, inStock, time.Now(), id)
return err
}
3 事务处理与一致性
事务用于保证跨多次写操作的原子性。一旦出现错误应回滚,成功则提交。
3.1 标准事务示例
go
func UpdateInventoryWithOrder(ctx context.Context, db *sql.DB, productID int64, qty int) error {
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
if err != nil { return err }
defer func() { if err != nil { _ = tx.Rollback() } }()
// 扣减库存
if _, err = tx.ExecContext(ctx, `UPDATE products SET in_stock = (in_stock - ?) WHERE id=?`, qty, productID); err != nil {
return err
}
// 创建订单
if _, err = tx.ExecContext(ctx, `INSERT INTO orders(product_id,quantity,created_at) VALUES(?,?,NOW())`, productID, qty); err != nil {
return err
}
return tx.Commit()
}
3.2 事务函数式封装
go
func WithTx(ctx context.Context, db *sql.DB, fn func(*sql.Tx) error) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil { return err }
if err := fn(tx); err != nil { _ = tx.Rollback(); return err }
return tx.Commit()
}
这种封装能提升清晰度,同时确保出错即回滚。
4 查询优化最佳实践
- 使用索引:为高频过滤、排序列创建合适索引,避免全表扫描。
- 预编译语句:使用
db.PrepareContext复用 SQL,提高频繁执行语句的吞吐。 - 批量写入:在事务中批量执行插入/更新,减少往返与锁开销。
- 控制列:只查询必要列,减少 I/O 与解码成本。
- 分页优化:避免深分页,尽量采用基于游标或主键的翻页。
示例:批量插入(事务 + 预编译)
go
func BatchInsertProducts(ctx context.Context, db *sql.DB, items []model.Product) error {
return WithTx(ctx, db, func(tx *sql.Tx) error {
stmt, err := tx.PrepareContext(ctx, `INSERT INTO products(name,price,category,in_stock,created_at,updated_at) VALUES(?,?,?,?,?,?)`)
if err != nil { return err }
defer stmt.Close()
now := time.Now()
for _, p := range items {
if _, err := stmt.ExecContext(ctx, p.Name, p.Price, p.Category, p.InStock, now, now); err != nil {
return err
}
}
return nil
})
}
5 完整示例:商品服务的数据层与HTTP接口
该示例整合连接池、查询构建器、仓储层与事务,提供两个端点:查询商品与创建订单。
go
package main
import (
"context"
"database/sql"
"encoding/json"
"log"
"net/http"
"time"
_ "github.com/go-sql-driver/mysql"
)
var (
db *sql.DB
repo *ProductRepo
)
func main() {
// 初始化数据库
cfg := db.Config{
Driver: "mysql",
DSN: "user:pass@tcp(localhost:3306)/shop?parseTime=true&charset=utf8mb4",
MaxOpenConns: 50,
MaxIdleConns: 20,
ConnMaxLifetime: 30 * time.Minute,
ConnMaxIdleTime: 10 * time.Minute,
ConnectTimeout: 5 * time.Second,
}
var err error
db, err = db.Open(cfg)
if err != nil { log.Fatal(err) }
repo = NewProductRepo(db)
// 路由
http.HandleFunc("/products", listProducts)
http.HandleFunc("/orders", createOrder)
http.HandleFunc("/db/stats", db.StatsHandler(db))
log.Println("服务启动: http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func listProducts(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
// 使用轻量查询构建器
sqlStr, args := query.Select("products", "id", "name", "price", "category").
Where("in_stock = ?", true).OrderBy("id DESC").Limit(20).SQL()
rows, err := db.QueryContext(ctx, sqlStr, args...)
if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError); return }
defer rows.Close()
var result []map[string]interface{}
for rows.Next() {
var id int64; var name, category string; var price float64
if err := rows.Scan(&id, &name, &price, &category); err != nil { continue }
result = append(result, map[string]interface{}{
"id": id, "name": name, "price": price, "category": category,
})
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(result)
}
func createOrder(w http.ResponseWriter, r *http.Request) {
type reqBody struct{ ProductID int64; Qty int }
var body reqBody
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "请求体错误", http.StatusBadRequest); return
}
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
if err := UpdateInventoryWithOrder(ctx, db, body.ProductID, body.Qty); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError); return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
通过本章的循序渐进学习,你应已掌握从连接池到查询构建器、从事务封装到性能优化的关键技能。建议在项目中建立统一的数据访问规范,既保证可维护性,又保留对性能的精细控制。