数据库访问层最佳实践
同样的数据库,接入方式不同,稳定性可以相差一个量级。本章汇总应用侧访问数据库时最容易踩坑、也最值得规范化的几个点,示例以 Go 为主,但原则通用。
连接池
连接是昂贵资源。池太小会排队、池太大压垮数据库。经验公式:
Warning
SetMaxIdleConns 小于 SetMaxOpenConns 时,高并发后大量连接会被关闭又重建,产生「连接抖动」。两者保持一致通常是更稳的选择。SetConnMaxLifetime 必须设置,否则中间件(如云数据库代理)单方面断开空闲连接会导致偶发 invalid connection。
超时与 context
每一条查询都应带超时。没有超时的查询会在数据库卡顿时耗尽调用方资源,引发雪崩。
分层超时应自上而下收紧或一致:HTTP 请求超时 ≥ 数据库操作超时,避免上游早已放弃、下游仍在苦算。
重试与幂等
只重试可安全重试的操作,并对瞬时错误重试:
Tip
重试必须配合幂等。对于「创建订单」「扣款」等写操作,用幂等键(Idempotency Key)保证重复执行不产生副作用:唯一约束 + INSERT ... ON CONFLICT DO NOTHING,或先查幂等表再执行。否则重试会变成重复下单。
参数化与语句复用
- 始终使用占位符(
?/$n),杜绝拼接。 - 复用 prepared statement 可减少解析开销:
事务管理
原则:
- 短小:事务内只做数据库操作,绝不包含网络调用、文件 IO、消息发送。
- 固定加锁顺序:多行/多表更新按统一顺序,降低死锁概率。
- 失败即返回:让
defer Rollback兜底,避免忘记回滚导致长事务。
错误处理
sql.ErrNoRows是正常的「无数据」,必须单独处理。- 死锁/锁等待可按驱动错误码识别并重试。
- 用
errors.Is/As而非字符串匹配错误信息。
批量与流式
健康检查与优雅关闭
- 启动时
Ping验证连通性;就绪探针可复用轻量查询。 - 停机时先停止接收新请求,等待在途请求完成,再关闭连接池。
- 为连接池设置合理的
ConnMaxLifetime,配合数据库滚动升级。
小结
- 连接池按公式估算并设上限,
MaxIdle=MaxOpen,必须设ConnMaxLifetime。 - 每条查询带超时;分层超时自上而下收敛。
- 只重试瞬时错误,重试必须幂等(幂等键 + 唯一约束)。
- 一律参数化、必要时复用 prepared statement;事务短小、失败即返回。
- 正确区分
ErrNoRows与系统错误;批量/流式处理大结果集。