跳转至

数据库与 context — 一条从连接池到优雅关闭的完整链

这一章讲什么: Go 程序怎么跟数据库打交道——从开一个连接池、跑查询, 到用 GORM 省去样板、用事务保原子性;以及 context:一个贯穿全部调用的 「 Deadline + 取消信号 + 行李牌」机制。 它在全书链条里的位置: 第 07 章的 REST API 把这里的 repository 包进 HTTP handler; 第 10 章的部署把这里的优雅关闭接进容器编排。这一章是「后端」二字的地基。 主线例子(贯穿本章): 一个「下单」操作——建连接池 → 开启事务 → 插订单、 扣库存 → 提交或回滚 → 全程 3 秒超时兜底 → 服务重启时优雅收尾。每一步都有具体代码。

1. 顶层全景:数据层的一张结构图

  HTTP handler(第 07 章)──ctx──► service ──ctx──► repository ──ctx──► database/sql ──► 驱动 ──► PostgreSQL/MySQL
                                     │                   │                │
                                     │                   │                ├─ *sql.DB = 连接池(不是单连接!)
                                     │                   │                ├─ *sql.Tx = 事务
                                     │                   │                └─ QueryContext/ExecContext/BeginTx
                                     │                   └─ GORM 可选:模型、CRUD、迁移
                                     └─ context 贯穿:超时 ▪ 取消 ▪ 关联 ID
  图说:ctx 从入口一路传到底,任何一层超时,全链同时收工。这是本章的脊柱。

2. database/sql:一个接口,三个类型

解决什么问题: 市面上有几十种数据库,Go 不想为每种写一个 API。database/sql 提供一套统一接口;真正的协议细节由各数据库的驱动包实现(驱动在 init 里 自注册一个名字,如 "postgres"、"mysql"——第 03 章那个下划线导入的兑现)1。

三个核心类型2:

类型 是什么 什么时候用
*sql.DB 连接池(不是一个连接!),管连接的创建、复用、超时 99% 的日常
*sql.Conn 钉死到单条连接的把柄 要保证多次操作在同一连接上
*sql.Tx 事务上下文,一串 SQL 原子执行 多步写操作

sql.DB 可被所有 goroutine 共享——内部已经做好了并发安全3。 配套方法三个动词:Exec(执行不返回行的语句)、Query(返回多行游标——游标:指向结果集中当前位置、可逐行往前推进的把柄)、 QueryRow(只取一行的优化路径)4。

连接驱动选型(原书给了社区现状)5:

数据库 经典驱动 新派驱动
PostgreSQL github.com/lib/pq github.com/jackc/pgx/v5(更快、功能多)
MySQL github.com/go-sql-driver/mysql(事实标准) —

3. 连接:连接串、Ping、池参数

3.1 连接串与「Open 不等于连通」

PostgreSQL(键值式)6:

connStr := "host=localhost port=5432 user=appuser password=secret dbname=appdb sslmode=disable"
db, err := sql.Open("postgres", connStr)

MySQL(DSN 式)6:

dsn := "appuser:secret@tcp(localhost:3306)/appdb?parseTime=true&charset=utf8mb4"
db, err := sql.Open("mysql", dsn)

一个反直觉的点:sql.Open 不真的连数据库——它只造出池的把柄。 必须 db.Ping():这一步才验证账号、网络、库名全对6。 程序启动时 Ping 失败就 log.Fatal,把「配错了」暴露在最早的时刻。

3.2 池的四个旋钮

db.SetMaxOpenConns(50)              // 最多同时开几条连接
db.SetMaxIdleConns(25)              // 闲置保留几条(热了不用重建)
db.SetConnMaxLifetime(30*time.Minute) // 一条连接最长活多久(防「陈旧的连接」)
db.SetConnMaxIdleTime(5*time.Minute)  // 一条连接闲多久就被回收

为什么要管:PostgreSQL 每条连接在服务端是一个独立进程——池开太大, 内存先被数据库那边吃光;MySQL 轻些,但池过小,请求排队,延迟照样飙升7。 调参原则(原书):匹配负载、别超配、定期轮换、看指标、查询短促8。 池的指标(open/in-use/wait 时长)可以暴露给 Prometheus 指导调优9。

3.3 安全清单

  • 生产别关 SSL(sslmode=disable 只配本地开发)10;
  • 密码别写死在代码里——环境变量(操作系统里给程序读的键值对配置,程序启动时现取)或密钥管理器;
  • 池上限先测算再放行,别让应用打垮数据库;
  • MySQL 记得 parseTime=true,否则时间戳会被当成字节数组而不是 time.Time10。

主线例子第 ① 步: 下单服务启动,建池、Ping、设四个旋钮:

db, err := sql.Open("pgx", dsn)
if err != nil { log.Fatalf("db open failed: %v", err) }
db.SetMaxOpenConns(50)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(30 * time.Minute)
db.SetConnMaxIdleTime(5 * time.Minute)
if err := db.Ping(); err != nil { log.Fatalf("db ping failed: %v", err) }

4. 查询与扫描:低层 API 的正确姿势

单行——注意错误不在 QueryRow 返回,而在 Scan 时才出现:

var name string
var age int
err := db.QueryRowContext(ctx,
    "SELECT name, age FROM users WHERE id=$1", userID).Scan(&name, &age)
if err != nil {
    if err == sql.ErrNoRows { /* 没这条记录,单独处理 */ }
    return err
}

多行——游标式逐行读,流式处理大结果集,不全量进内存:

rows, err := db.QueryContext(ctx, "SELECT id, email FROM subscribers WHERE active=true")
if err != nil { return err }
defer rows.Close()
for rows.Next() {
    var id int
    var email string
    if err := rows.Scan(&id, &email); err != nil { return err }
    // 处理这一行
}
if err := rows.Err(); err != nil { return err }   // 循环正常结束≠没出错

原书的五条扫描纪律:循环外一次声明变量(少分配)、扫进原生类型、 用 Context 版查询、defer rows.Close() 紧跟查询、rows.Err() 收尾检查11。 ——全是第 03、04 章纪律(早返回、defer、查 error)的复用,没有一条是新知识, 这就是 Go 的风格:机制少,纪律多。

参数怎么传: 永远用 $1、? 这类占位符传参,驱动会做预编译语句 (prepared statement)——SQL 结构和用户数据分开,注入攻击无路可走12。

5. GORM:省样板,不省理解

ORM(Object-Relational Mapping,对象-关系映射)把表映射成结构体, CRUD 不用手写 SQL。GORM 是 Go 生态最流行的一个13。

模型即表,用 struct tag 控制列属性:

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Name      string    `gorm:"size:100;not null"`
    Email     string    `gorm:"uniqueIndex"`
    Age       int
    CreatedAt time.Time
}

查询可读性优先:

db.First(&user, 42)                       // 主键取一条
db.Where("age > ?", 18).Order("created_at desc").Limit(20).Find(&users)
db.Model(&User{}).Select("name", "email").Scan(&results)
db.Preload("Orders").First(&user)         // 预加载关联,免手写 JOIN

迁移( schema 演变):db.AutoMigrate(&User{}, &Order{}) 按模型建表/补列/ 建索引——但它从不删列,防误删数据14。生产上更常用带版本号的迁移工具 (golang-migrate、goose、dbmate),迁移脚本进版本库15。

GORM 与手写 SQL 并存: CRUD、关系、迁移交给 GORM;复杂分析查询、 批量操作、数据库特性优化用 db.Raw("SELECT COUNT(*) ...")16。 原书还点了一类典型性能病:N+1 查询(取一个列表时对每条记录再查一次关联—— 本该一次 JOIN 或 Preload 解决),并指出这类模式 AI 审查比人眼更容易抓到17。

判断(我们的,不是书里的): GORM 这类 ORM 在 Go 社区的争议点原书没展开—— 它隐藏 SQL,复杂查询调试时「生成的 SQL 是什么」要 db.Debug() 才看得见。 原书的选择(并存策略)是对的:默认手写 SQL 保透明,样板实在多的地方用 GORM。 全项目 GORM 化的团队,迟早会在一个慢查询上花掉省下的全部时间。 如果错,会错在: 如果团队里人人都熟 SQL 且查询不复杂,ORM 的省样板收益 确实可观——「手写优先」是保守默认,不是绝对律令。

5.1 N+1:ORM 时代最经典的性能病

GORM 把取数据变简单之后,最常见的慢查询不是 SQL 写错,而是N+1:

var users []User
db.Find(&users)                  // 1 条查询取出 N 个用户
for _, u := range users {
    db.Where("user_id = ?", u.ID).Find(&u.Orders)   // 又 N 条,一人一条
}

取 100 个用户 = 101 条查询。数据库来回的网络开销把每条查询的真实成本放大 几十倍,而且 ORM 的写法看着如此自然,review 时极易漏看。两个修法:

  1. 预加载:db.Preload("Orders").Find(&users)——GORM 自动改成一条 IN 查询 (或一次 JOIN),把 N+1 压回 2 条;
  2. 手 JOIN:复杂关联直接 db.Raw("SELECT ... JOIN ..."),第 06 章的混合策略。

原书在这一节专门点了 AI 的用处:这类模式(嵌套循环里藏查询)是模式识别器 最擅长抓的形状,贴一段 handler 代码让它「找 N+1」,比人扫一遍更可靠—— 但修法选预加载还是 JOIN,仍要你看数据量和索引后定。

5.2 迁移的两条路:AutoMigrate 与版本化脚本

  • AutoMigrate 适合开发期:模型一改,表自动跟上;只增不删的保护(原书明写) 防止误删列,但也意味着它不会帮你清理——重命名一列的结果是新列加旧列并存;
  • 版本化迁移(golang-migrate、goose、dbmate)适合生产:每个变更一个带编号的 SQL 文件,进版本库、可回滚、可在 CI 里对干净数据库重放验证。生产纪律只有一条: 表结构怎么变,代码就怎么变,二者同一个 PR、同一次发布。

6. 事务:显式三步,defer 保命

解决什么问题: 下单要插订单行 + 扣库存行 + 记日志行,中途任何一步失败, 数据库不能留下「扣了库存没订单」的烂账。事务保证一串操作要么全成、要么全不算。

Go 的姿势极其显式:Begin → 干活 → 成了 Commit、败了 Rollback,全写在面上18:

func (r *OrderRepo) CreateOrder(ctx context.Context, o *Order) error {
    tx, err := r.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
    if err != nil { return err }

    if _, err := tx.ExecContext(ctx,
        `INSERT INTO orders (user_id, amount) VALUES ($1, $2)`, o.UserID, o.Amount); err != nil {
        tx.Rollback()
        return fmt.Errorf("insert order: %w", err)
    }
    if _, err := tx.ExecContext(ctx,
        `UPDATE inventory SET stock = stock - 1 WHERE product_id=$1`, o.ProductID); err != nil {
        tx.Rollback()
        return fmt.Errorf("inventory update: %w", err)
    }
    return tx.Commit()
}

主线例子第 ② 步到此就位。 原书强调三条纪律19:

  1. defer tx.Rollback() 的写法——Commit 成功了再调 Rollback 也无害, 让 defer 替你把「任何早返回路径」都兜住,比上面逐条手写 Rollback 更稳;
  2. 事务要短——长事务占着锁拖垮别人;校验和查询尽量放到 Begin 之前;
  3. 用 Context 版(BeginTx(ctx, ...)、*Context 方法)——超时或取消 时事务自动回滚,连接归还池子。

错误分类(retryable 与否):PostgreSQL 的序列化冲突(这里的「序列化」不是数据格式转换,而是数据库隔离级别的一种:让并发事务像排队一样一个个执行,SQLSTATE 40001)是 瞬时冲突,可以安全重试;用结构化错误包装(第 04 章)把错误码带出来, 重试循环就有据可依20。GORM 的事务助手 db.Transaction(func(tx) error {...}) 语义相同:函数返回 nil 提交,返回 error 回滚——但底层语义要懂,不然 嵌套事务、部分更新的怪 bug 找上门时只会盯着 ORM 发呆21。

7. context:全链路的 Deadline、取消与行李牌

解决什么问题: 一个 HTTP 请求进来,穿过中间件、handler、service、 数据库;用户关了浏览器、负载均衡器超时、管理员手动取消——这个「别干了」 的信号怎么一路传到最深处? 靠到处传布尔标志或全局变量,必然漏。

context.Context 就是合同:每个可能阻塞的操作,第一个参数都收 ctx; 上游死了,下游立刻停。 HTTP 请求到达时,服务器自动生成一个携带 连接生命周期的 ctx,之后每一层透传22。

7.1 超时:给操作掐表

ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()                        // 释放资源,别省
rows, err := db.QueryContext(ctx, "SELECT * FROM large_table")

两秒一到,驱动中断查询、连接还池,函数带着 context.DeadlineExceeded 返回23。 分布式里的关键动作是「透传剩余预算」:A 调 B 时把 ctx 传过去, B 自然知道自己只剩多少秒,不会从头再起一个全量超时24。

7.2 取消:最快的答案赢得一切

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
results := make(chan string, 2)
go func() { r, _ := fastQuery(ctx); results <- r; cancel() }()  // 快者归来, cancel() 杀掉慢者
go func() { r, _ := slowQuery(ctx); results <- r }()
answer := <-results

cancel() 一调,所有拿着这个 ctx 的 goroutine 在各自的 <-ctx.Done() 处醒来退出 ——第 05 章竞态与泄漏问题的标准解25。

7.3 传值:行李牌,不是搬运工

context.WithValue 可以把「关联 ID」「登录用户」这类跟着请求生死的小物件 挂进 ctx,深层函数不用加参数也能读到26:

type key int                 // 自建类型当 key,防撞名
const requestIDKey key = 0
ctx = context.WithValue(ctx, requestIDKey, reqID)
// 深处:log.Printf("request=%s ...", ctx.Value(requestIDKey))

原书转述了官方文档的强烈告诫:ctx 值要小、要省着用——它不是结构体的替代品, 只装「与请求生命周期绑定的、穿透深调用链会弄脏签名」的数据27。

7.4 不带 context 的裸调用是债

原书在架构原则里把话说绝:生产代码里不许出现不带 ctx 的 Query/Exec—— 这是硬性要求,不是风格偏好28。理由:客户端断连时,昂贵的查询继续跑完, 纯属烧数据库的 CPU 和锁。

7.5 三层架构的完整代码骨架(把第 07 章的签名对上)

第 9 节那张签名表,配上最小的实现长这样(书名与库存字段为演示):

// repository:唯一摸 SQL 的层
func (r *BookRepo) FindByID(ctx context.Context, id int64) (*Book, error) {
    row := r.db.QueryRowContext(ctx,
        "SELECT id, title, stock FROM books WHERE id=$1", id)
    var b Book
    if err := row.Scan(&b.ID, &b.Title, &b.Stock); err != nil {
        return nil, fmt.Errorf("FindByID: %w", err)   // 包装,带上层名
    }
    return &b, nil
}

// service:编排,不碰 SQL
func (s *BookService) GetBook(ctx context.Context, id int64) (*Book, error) {
    return s.repo.FindByID(ctx, id)                    // ctx 原样透传,不重建
}

// handler:翻译 HTTP ⇄ 领域
func (h *BookHandler) GetBook(w http.ResponseWriter, r *http.Request) {
    ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
    defer cancel()
    id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64)
    book, err := h.service.GetBook(ctx, id)
    if err != nil {
        writeError(w, http.StatusNotFound, "book not found")   // 第 07 章的信封
        return
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(book)
}

三层各一行纪律:repository 每个错误都包层名;service 不造 ctx、不透传算输; handler 只管协议翻译(路径参数、状态码、JSON)。review 时三层的 diff 应该 一眼能说出是哪层的活——说不出,就是层渗了。

7.6 池参数的失效模式(反着学)

四个旋钮各有一种「调错方向」的典型事故(演示性描述):

旋钮 调错方向 事故形状
MaxOpen 太大 数据库先垮 PG 每连接一个进程,几百连接吃光内存,查询全面变慢
MaxOpen 太小 应用排队 请求在池外干等,延迟飙,ctx 超时齐发
MaxIdle 太小 反复握手 每个查询都重建 TCP+认证,延迟里一半是握手
Lifetime 不设 撞上云负载均衡器 空闲连接被 LB 悄悄断掉,应用拿到死连接报错

最后一行是云时代特有的:云上数据库前面的负载均衡会回收「太久没说话」的连接, 所以 Lifetime 要小于对方的回收时限——这是原书「定期轮换」原则背后的具体 威胁模型。

8. 优雅关闭:发布不炸毛的五步

云环境天天重启(发版、扩缩容),服务必须有条不紊地死:

  1. 停收新请求:srv.Shutdown(ctx) 让监听器停止接受;
  2. 播发关闭 context:带 10 秒 Deadline 的根 ctx 传遍各组件;
  3. 在途请求干完或超时:handler 手里的 ctx 自然到期;
  4. 关连接池:db.Close();
  5. 全程日志可审计29。
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
<-stop                                    // 等到 Ctrl+C 或编排器的停止信号
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
srv.Shutdown(ctx)
db.Close()

(SIGTERM 是操作系统/容器编排器发来的「请退出」信号;signal.Notify 让程序 接得住它而不是被硬杀。29)

主线例子第 ③ 步: 下单服务发版。在途的下单事务要么 10 秒内提交完成, 要么随 ctx 到期自动回滚——不会出现「半笔订单」。

9. 收成三层架构:handler → service → repository

原书把全章收成一张签名对照表,这套形状在成百上千个 endpoint 上都稳定30:

层 签名 职责
handler GetUser(w, r *http.Request) 从 r.Context() 取 ctx,设超时,翻译错误为 HTTP 响应
service GetUser(ctx, id) 业务规则、编排多个 repository
repository GetUserByID(ctx, id) 只跟 SQL 打交道,把错误包 %w 传出

ctx 从不丢失、从不重建——这是铁律30。中间件统一造超时(比如全站 2 秒), 每个 endpoint 继承同一政策31。OpenTelemetry 之类的分布式追踪库自动把 span 绑在 ctx 上,API 和数据库边界连成一条 trace32。

全章汇成六条架构原则33:每条 DB 操作必带 ctx;数据库初始化集中一处; 关闭语义全服务共享;错误包装 + 关联 ID + 结构化日志;用短 Deadline 的 ctx 做 压力测试;AI 辅助查反模式(缺 ctx 的查询、永不取消的函数、池配置不当)。

10. 作者的判断与证据

硬事实: sql.DB 是池、Open 不连通要 Ping、占位符防注入、事务 API 形态、 ctx 超时取消语义、四池参数、Shutdown 流程——全部官方文档级事实261929。 经验主张: 池参数的具体数值(50/25/30 分钟)是示例不是定律,原书也明说 要按负载匹配8;「生产禁止裸 Query/Exec」是社区纪律的强表述28。

11. 边界与局限

  • NoSQL 只在章首提了一句,MongoDB/Redis 的具体交互整章没有——标题里的 「NoSQL」名不副实。
  • pgx 的高级特性(JSONB、数组、二进制协议)只列了清单,没示例34。
  • 测试策略一笔带过(Docker 起库 + 事务回滚),没形成可照做的章节。
  • AI 部分最薄:只有配置脚手架与 N+1 识别两处,没有数据库专属的 AI 工作流。

12. 可带走的

  1. database/sql = 统一接口;驱动 init 自注册;三类型:DB(池)、Conn、Tx。
  2. sql.Open 不连通,db.Ping() 才验证。
  3. 池四旋钮:MaxOpen / MaxIdle / 存活期 / 空闲期;PostgreSQL 每连接一个服务端进程,别超配。
  4. 查询三动词:Exec / Query / QueryRow;占位符传参防注入。
  5. 扫描五纪律:外置声明、原生类型、Context 版、defer Close、Err 收尾。
  6. GORM:模型 tag、First/Where/Preload、AutoMigrate 只增不删、版本化迁移用 goose 等。
  7. 事务:BeginTx → defer Rollback → 成了 Commit;要短;ctx 到期自动回滚。
  8. context 三件套:WithTimeout 掐表、WithCancel 竞速取消、WithValue 挂关联 ID(小而省)。
  9. 生产硬规矩:裸 Query/Exec 不准出现;ctx 逐层透传不重建。
  10. 优雅关闭五步走:停收、播 ctx、等在途、关池、留日志。
  11. 三层签名:handler 造超时、service 编业务、repository 摸 SQL。
  12. AI 在本章的正当位置:配置脚手架、N+1 与缺 ctx 这类模式识别。

13. 原文地图

主题 原书章 原文位置
三类型与统一接口 6. Database Interaction and Context Management in Go text/16-ch06-6-database-interaction-and-context-management-in.txt:81(搜「three primary types」)
驱动选型 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:134(搜「Choosing the Appropriate Database Driver」)
连接串与 Ping 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:152(搜「Constructing Connection Strings」)text/16-ch06-6-database-interaction-and-context-management-in.txt:171(搜「does not validate」)
池参数与 PG 进程模型 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:181(搜「Connection Pool」)text/16-ch06-6-database-interaction-and-context-management-in.txt:198(搜「dedicated server process」)
安全清单 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:219(搜「Security Considerations」)
查询与扫描纪律 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:299(搜「Querying and Scanning」)text/16-ch06-6-database-interaction-and-context-management-in.txt:349(搜「Best Practices」)
GORM 与迁移 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:362(搜「Using GORM」);AutoMigrate 不删列见 :441
事务三步纪律 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:491(搜「Begin() or BeginTx()」)text/16-ch06-6-database-interaction-and-context-management-in.txt:500(CreateOrder 全例)
重试与隔离级别 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:570(搜「isolation level is configured explicitly」)text/16-ch06-6-database-interaction-and-context-management-in.txt:575(搜「retryable」)
context 超时 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:634(搜「Deadlines and Timeouts」)
context 取消竞速 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:663(搜「Cleaning up Resources」)
WithValue 告诫 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:693(搜「Request-Scoped Metadata」)text/16-ch06-6-database-interaction-and-context-management-in.txt:717(搜「strongly cautions」)
池四参数调优 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:845(搜「Connection Pool Tuning」)
优雅关闭生命周期 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:894(搜「Lifecycle of a Graceful Shutdown」)text/16-ch06-6-database-interaction-and-context-management-in.txt:905(完整代码)
三层架构签名 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:992(搜「Architecture of a Context-Aware Data Layer」)
六条架构原则 同上 text/16-ch06-6-database-interaction-and-context-management-in.txt:949(搜「Key Architectural Principles」)


  1. 出处:「6. Database Interaction and Context Management in Go」第 68–80 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:69,搜「database/sql package serves」)。驱动自注册与「driver packages」见 :74。 ↩

  2. 出处:同章第 81–92 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:81,搜「three primary types」)。 ↩↩

  3. 出处:同章第 111–112 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:111,搜「designed with concurrency in mind」)。 ↩

  4. 出处:同章第 93–102 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:94,搜「Exec and Query methods」)。 ↩

  5. 出处:同章第 134–150 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:135,搜「widely adopted driver」)。 ↩

  6. 出处:同章第 152–179 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:152,搜「Constructing Connection Strings」;「does not validate the connection immediately」见 :171,Ping 见 :174)。 ↩↩↩↩

  7. 出处:同章第 181–201 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:181,搜「Understanding sql.DB」;「dedicated server process」见 :198)。 ↩

  8. 出处:同章第 870–885 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:870,搜「Principles for Effective Pool Tuning」)。 ↩↩

  9. 出处:同章第 1110–1125 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:1110,搜「Connection Pool Behavior」)。 ↩

  10. 出处:同章第 219–235 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:219,搜「Security Considerations」)。 ↩↩

  11. 出处:同章第 299–360 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:34,搜「Querying and Scanning Rows Efficiently with database/sql」;五条纪律见 :349)。 ↩

  12. 出处:同章第 103–110 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:103,搜「Prepared statements」)。 ↩

  13. 出处:同章第 362–370 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:366,搜「most widely adopted ORM」)。 ↩

  14. 出处:同章第 426–442 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:427,搜「built-in migration engine」;「does not remove existing columns」见 :441)。 ↩

  15. 出处:同章第 443–449 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:443,搜「Versioned Migrations」)。 ↩

  16. 出处:同章第 451–477 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:451,搜「Combining Manual SQL」;db.Raw 例见 :457)。 ↩

  17. 出处:同章第 473–477 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:474,搜「N+1 queries」)。 ↩

  18. 出处:同章第 478–498 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:479,搜「Transactions form the backbone」)。 ↩

  19. 出处:同章第 499–561 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:499,搜「CreateOrder」;「defer tx.Rollback()」见 :505;事务要短见 :551)。 ↩↩

  20. 出处:同章第 574–579 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:575,搜「retryable」;SQLSTATE 40001 见 :576)。 ↩

  21. 出处:同章第 580–599 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:580,搜「simplify transaction handling」)。 ↩

  22. 出处:同章第 609–632 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:609,搜「Mastering Context」;请求到达自动生成 ctx 见 :622)。 ↩

  23. 出处:同章第 634–661 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:634,搜「Deadlines and Timeouts」;驱动监听取消见 :651)。 ↩

  24. 出处:同章第 656–661 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:657,搜「propagate the remaining time budget」)。 ↩

  25. 出处:同章第 663–691 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:663,搜「Cleaning up Resources」;fastQuery/slowQuery 代码见 :672)。 ↩

  26. 出处:同章第 693–722 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:693,搜「Request-Scoped Metadata」;key 类型例见 :701)。 ↩

  27. 出处:同章第 716–722 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:717,搜「strongly cautions」)。 ↩

  28. 出处:同章第 949–967 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:949,搜「Key Architectural Principles」)。「No Query or Exec calls should appear in production code」为原文明写。 ↩↩

  29. 出处:同章第 887–940 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:887,搜「Graceful Shutdowns」;五步见 :894;完整 main 示例见 :905)。 ↩↩↩

  30. 出处:同章第 992–1018 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:992,搜「Architecture of a Context-Aware Data Layer」)。「context never gets lost, or is recreated」见 :1017。 ↩↩

  31. 出处:同章第 1039–1057 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:1039,搜「Deadlines and Timeouts」;中间件统一管理见 :1056)。 ↩

  32. 出处:同章第 1093–1108 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:1093,搜「Observability and Context」)。 ↩

  33. 出处:同章第 949–967 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:949,搜「Key Architectural Principles」)。 ↩

  34. 出处:同章第 237–259 段(text/16-ch06-6-database-interaction-and-context-management-in.txt:237,搜「Advanced PostgreSQL Considerations」)。 ↩