惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
Jina AI
Jina AI
S
SegmentFault 最新的问题
博客园 - 【当耐特】
阮一峰的网络日志
阮一峰的网络日志
IT之家
IT之家
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
aimingoo的专栏
aimingoo的专栏
博客园 - 叶小钗
The Cloudflare Blog
MyScale Blog
MyScale Blog
F
Full Disclosure
T
Tailwind CSS Blog
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
B
Blog
Apple Machine Learning Research
Apple Machine Learning Research
Engineering at Meta
Engineering at Meta
P
Privacy & Cybersecurity Law Blog
H
Help Net Security
A
Arctic Wolf
T
Tor Project blog
WordPress大学
WordPress大学
Cisco Talos Blog
Cisco Talos Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Project Zero
Project Zero
V2EX - 技术
V2EX - 技术
C
CERT Recently Published Vulnerability Notes
L
Lohrmann on Cybersecurity
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
S
Securelist
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
PCI Perspectives
PCI Perspectives
雷峰网
雷峰网
J
Java Code Geeks
G
GRAHAM CLULEY
D
DataBreaches.Net
C
CXSECURITY Database RSS Feed - CXSecurity.com
Attack and Defense Labs
Attack and Defense Labs
美团技术团队
Security Archives - TechRepublic
Security Archives - TechRepublic
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
D
Docker
Cloudbric
Cloudbric
L
LangChain Blog

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第16章 - 数据库编程
我才是银古 · 2026-06-19 · via 博客园 - 我才是银古

第16章 - 数据库编程

数据持久化是后端开发的核心需求。本章讲解 Go 操作数据库的方式:标准库 database/sql、主流驱动、ORM 框架,以及连接池、事务、预处理等关键概念。

16.1 database/sql 概述

database/sql 是 Go 标准库提供的数据库访问抽象层。它本身不实现具体数据库的通信协议,而是定义了一套通用接口,由各数据库的驱动实现。这种设计使得切换数据库时业务代码改动最小。

常见数据库驱动:

数据库 驱动包
MySQL github.com/go-sql-driver/mysql
PostgreSQL github.com/lib/pqgithub.com/jackc/pgx
SQLite github.com/mattn/go-sqlite3
SQL Server github.com/microsoft/go-mssqldb

16.2 连接数据库

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql" // 空白导入,注册驱动
)

func main() {
    // dsn 格式: 用户名:密码@tcp(主机:端口)/数据库名?参数
    dsn := "user:password@tcp(127.0.0.1:3306)/mydb?charset=utf8mb4&parseTime=true"
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // sql.Open 不会立即建立连接,用 Ping 验证连通性
    if err := db.Ping(); err != nil {
        log.Fatal("数据库无法连接:", err)
    }
}

注意:驱动通过空白导入 _ 注册,其 init 函数会调用 sql.Registersql.Open 的第一个参数即注册的驱动名。

16.3 连接池配置

*sql.DB 实际上是一个连接池,并发安全,应在程序生命周期内复用同一个实例(而非每次操作都 Open):

db.SetMaxOpenConns(25)                 // 最大打开连接数
db.SetMaxIdleConns(25)                 // 最大空闲连接数
db.SetConnMaxLifetime(5 * time.Minute) // 连接最大存活时间
db.SetConnMaxIdleTime(time.Minute)     // 连接最大空闲时间

合理配置连接池对高并发服务的稳定性至关重要。连接数过小会限制吞吐,过大会压垮数据库。

16.4 执行查询

16.4.1 查询单行

type User struct {
    ID   int
    Name string
    Age  int
}

func getUser(db *sql.DB, id int) (*User, error) {
    var u User
    // 使用占位符防止 SQL 注入(MySQL 用 ?,PostgreSQL 用 $1)
    row := db.QueryRow("SELECT id, name, age FROM users WHERE id = ?", id)
    err := row.Scan(&u.ID, &u.Name, &u.Age)
    if err != nil {
        if err == sql.ErrNoRows {
            return nil, fmt.Errorf("用户 %d 不存在", id)
        }
        return nil, err
    }
    return &u, nil
}

16.4.2 查询多行

func listUsers(db *sql.DB) ([]User, error) {
    rows, err := db.Query("SELECT id, name, age FROM users WHERE age > ?", 18)
    if err != nil {
        return nil, err
    }
    defer rows.Close() // 必须关闭,避免连接泄漏

    var users []User
    for rows.Next() {
        var u User
        if err := rows.Scan(&u.ID, &u.Name, &u.Age); err != nil {
            return nil, err
        }
        users = append(users, u)
    }
    // 检查遍历过程中是否出错
    if err := rows.Err(); err != nil {
        return nil, err
    }
    return users, nil
}

关键点:查询多行后必须 defer rows.Close(),并在循环后检查 rows.Err()

16.5 增删改操作

Exec 用于执行不返回行的语句(INSERT、UPDATE、DELETE):

func createUser(db *sql.DB, name string, age int) (int64, error) {
    result, err := db.Exec(
        "INSERT INTO users (name, age) VALUES (?, ?)",
        name, age,
    )
    if err != nil {
        return 0, err
    }
    id, err := result.LastInsertId() // 获取自增主键
    return id, err
}

func updateAge(db *sql.DB, id, age int) error {
    result, err := db.Exec("UPDATE users SET age = ? WHERE id = ?", age, id)
    if err != nil {
        return err
    }
    rows, _ := result.RowsAffected() // 受影响行数
    fmt.Printf("更新了 %d 行\n", rows)
    return nil
}

16.6 防止 SQL 注入

始终使用参数化查询(占位符),永远不要用字符串拼接构造 SQL

// 危险!存在 SQL 注入漏洞
db.Query("SELECT * FROM users WHERE name = '" + name + "'")

// 安全:使用占位符,参数由驱动安全转义
db.Query("SELECT * FROM users WHERE name = ?", name)

参数化查询不仅防注入,还能利用预处理提升性能。

16.7 预处理语句

对于需要反复执行的 SQL,预处理(Prepared Statement)可提升性能并增强安全:

stmt, err := db.Prepare("INSERT INTO users (name, age) VALUES (?, ?)")
if err != nil {
    return err
}
defer stmt.Close()

for _, u := range users {
    if _, err := stmt.Exec(u.Name, u.Age); err != nil {
        return err
    }
}

16.8 事务

事务保证一组操作的原子性——要么全部成功,要么全部回滚:

func transfer(db *sql.DB, from, to int, amount float64) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    // 确保异常时回滚
    defer tx.Rollback() // 若已 Commit,Rollback 无副作用

    _, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from)
    if err != nil {
        return err // defer 中的 Rollback 会执行
    }

    _, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to)
    if err != nil {
        return err
    }

    return tx.Commit() // 提交事务
}

模式Begin 后立即 defer tx.Rollback(),成功路径以 tx.Commit() 结尾。Commit 之后的 Rollback 是无操作,因此这个模式既安全又简洁。

16.9 使用 Context 控制超时

Context 的方法(QueryContextExecContextBeginTx)可实现查询超时与取消:

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

rows, err := db.QueryContext(ctx, "SELECT ... FROM big_table")

生产环境强烈建议所有数据库操作都传入 context,避免慢查询拖垮服务。

16.10 ORM 与查询构建器

直接使用 database/sql 较为底层,需要手写 SQL 和扫描字段。社区提供了更高层的工具:

16.10.1 GORM

最流行的 Go ORM,支持模型映射、关联、钩子、自动迁移:

import "gorm.io/gorm"

type User struct {
    gorm.Model        // 内置 ID、CreatedAt、UpdatedAt、DeletedAt
    Name string
    Age  int
}

db.AutoMigrate(&User{})                  // 自动建表
db.Create(&User{Name: "Alice", Age: 30}) // 插入
var user User
db.First(&user, 1)                        // 主键查询
db.Where("age > ?", 18).Find(&users)      // 条件查询
db.Model(&user).Update("Age", 31)         // 更新
db.Delete(&user)                          // 软删除

16.10.2 sqlx

jmoiron/sqlxdatabase/sql 的轻量扩展,保留原生性能的同时简化字段扫描:

import "github.com/jmoiron/sqlx"

var users []User
db.Select(&users, "SELECT * FROM users WHERE age > ?", 18) // 自动映射到结构体

16.10.3 sqlc 与 ent

  • sqlc:根据 SQL 文件生成类型安全的 Go 代码,兼顾性能与安全。
  • ent:Facebook 开源的实体框架,图式化建模,代码生成。

16.10.4 选型建议

方案 适用场景
database/sql 追求极致控制和性能,简单查询
sqlx 想保留原生性能又简化扫描
GORM 快速开发,复杂关联,团队熟悉 ORM
sqlc 喜欢写 SQL,又要类型安全

16.11 NoSQL 与缓存

Go 也有成熟的 NoSQL 和缓存客户端:

  • Redisgithub.com/redis/go-redis
  • MongoDBgo.mongodb.org/mongo-driver
  • Elasticsearch:官方 go-elasticsearch
import "github.com/redis/go-redis/v9"

rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
rdb.Set(ctx, "key", "value", time.Hour)
val, err := rdb.Get(ctx, "key").Result()

16.12 本章小结

本章讲解了 Go 的数据库编程:标准库 database/sql 提供了统一的数据库抽象,配合各驱动操作不同数据库。我们学习了连接池配置、查询、增删改、参数化查询防注入、预处理、事务和 context 超时控制等核心技能。对于更高层抽象,GORM、sqlx、sqlc 等工具各有所长。掌握这些知识,你能构建出健壮的数据持久化层。

下一章我们将学习 Go 的工程化实践与综合项目实战。