
数据库(八):实战中的数据库——迁移、监控与故障案例
数据库的运维实践——模式迁移、连接池、监控、备份策略、托管数据库选型,以及来自生产环境的血泪教训。
理解数据库内部原理只是成功的一半;另一半是在生产环境中持续稳定运行它——不丢数据、不掉可用性,更别在凌晨三点被警报叫醒。本文聚焦于那些只能靠实战积累的运维知识:没人会在出事前告诉你,但一旦出事,你立刻就需要它们。
模式迁移:边飞行边换引擎#
你的数据库模式一定会变:新功能需要新字段、新表、新索引。真正的挑战在于如何在零停机的前提下完成演进?

迁移工具对比#
| 工具 | 语言 | 支持数据库 | 核心特性 |
|---|---|---|---|
| Flyway | Java(提供 CLI) | PostgreSQL、MySQL、Oracle、SQL Server | SQL + Java 迁移脚本,版本追踪 |
| Liquibase | Java(提供 CLI) | PostgreSQL、MySQL、Oracle、SQL Server | XML/YAML/JSON 变更日志,支持回滚 |
| golang-migrate | Go | PostgreSQL、MySQL、SQLite、更多 | CLI + 库集成,支持 up/down 迁移 |
| Alembic | Python(SQLAlchemy) | 所有 SQLAlchemy 支持的数据库 | 基于模型自动生成迁移 |
| Prisma Migrate | TypeScript | PostgreSQL、MySQL、SQLite、MongoDB | Schema-first,自动生成 SQL |
| dbmate | Go | PostgreSQL、MySQL、SQLite、ClickHouse | 简洁、无框架依赖 |
迁移文件结构示例#
| |
| |
| |
| |
迁移最佳实践#
向后兼容性(Backward Compatibility)#
永远不要破坏正在运行的应用程序。 迁移必须同时兼容旧代码和新代码:
| |
❌ 错误做法(立即破坏旧代码):
| |
✅ 正确做法(兼容新旧代码):
| |
在线 DDL(Online DDL)#
某些 DDL 操作会锁住整张表,阻塞读写——对于一亿行的表,这可能意味着数分钟不可用。
✅ 安全操作(无锁或极短锁,PostgreSQL):
| |
❌ 危险操作(全表重写或长时间锁):
| |
MySQL 生态中,pt-online-schema-change(Percona)和 gh-ost(GitHub)通过影子表(shadow table)实现在线变更:复制数据、捕获 binlog 变更、原子切换:
| |
零停机重命名策略#
重命名字段极具挑战:旧代码引用旧名,新代码引用新名:
| |
连接池(Connection Pooling)#
每个数据库连接都消耗资源:内存(PostgreSQL 中约 5–10 MB/连接)、文件描述符、CPU(用于进程/线程管理)。若无连接池,应用实例突发增长极易耗尽数据库连接上限。

问题场景#
| |
PgBouncer(PostgreSQL)#
| |
| |
ProxySQL(MySQL)#
| |
应用层连接池#
| |
| |
数据库监控#


关键指标#
| 指标 | 健康范围 | 异常时行动建议 |
|---|---|---|
| QPS(每秒查询数) | 基线 ± 20% | 排查突增或骤降原因 |
| 查询延迟 p99 | OLTP 场景 < 100 ms | 定位并优化慢查询 |
| 活跃连接数 | < 80% max_connections | 增加连接池大小或 max_connections |
| 复制延迟 | < 1 秒 | 检查副本负载、网络状况 |
| 缓冲池命中率 | > 99% | 增大缓冲池或缩小热数据集 |
| 磁盘 I/O 等待 | < 10% | 升级存储或增加 RAM 缓存 |
| 锁等待比例 | < 5% 事务数 | 缩小事务作用域 |
| 死锁/秒 | < 1 | 修复应用中锁获取顺序 |
| WAL 生成速率 | 基线 ± 30% | 检查写风暴 |
| 表膨胀率(PostgreSQL) | < 20% 死元组 | 调优 autovacuum |
PostgreSQL 监控查询#
| |
MySQL 监控查询#
| |
慢查询分析#


启用慢查询日志#
| |
| |
查询优化工作流#
| |
| |
备份策略#
逻辑备份(Logical Backups)#
逻辑备份导出为 SQL 语句或结构化数据文件。

| |
| |
物理备份(Physical Backups)#
物理备份直接拷贝数据文件,大型数据库下速度远超逻辑备份。
| |
| |
时间点恢复(Point-in-Time Recovery, PITR)#
PITR 允许恢复到任意时刻,不止是备份快照时间点。
| |
备份方式对比#
| 维度 | 逻辑备份(pg_dump) | 物理备份(pg_basebackup) | PITR |
|---|---|---|---|
| 备份速度 | 慢(SQL 层读取全量数据) | 快(文件拷贝) | 持续(WAL 流式归档) |
| 恢复速度 | 慢(重放 SQL) | 快(文件拷贝) | 中等(基础备份 + WAL 重放) |
| 存储大小 | 较小(压缩 SQL) | 较大(完整数据目录) | 基础备份 + WAL 文件 |
| 粒度 | 支持按表备份 | 仅支持集群级 | 任意时间点 |
| 版本兼容性 | 跨版本兼容 | 需同主版本 | 需同主版本 |
| 部分恢复 | 支持(指定表) | 不支持 | 不支持 |
备份验证(Backup Testing)#
从未验证过的备份,不是备份,只是希望。 务必定期执行恢复测试:
| |
托管数据库选型(Managed Database)#
自建数据库虽有助学习,但运维成本高昂。托管服务自动处理补丁、备份、复制与监控:
| 服务 | 提供商 | 支持引擎 | 核心特性 |
|---|---|---|---|
| RDS | 阿里云 / AWS | MySQL、PostgreSQL、SQL Server、MariaDB | 自动备份、多可用区 |
| PolarDB | 阿里云 | MySQL、PostgreSQL、Oracle 兼容 | 共享存储、最大 100 TB、读扩展 |
| AnalyticDB | 阿里云 | MySQL 兼容(OLAP) | PB 级分析、列式存储 |
| Aurora | AWS | MySQL、PostgreSQL 兼容 | 6 节点复制、自动扩缩容存储 |
| Cloud SQL | Google Cloud | MySQL、PostgreSQL、SQL Server | 自动故障转移、IAM 集成 |
| AlloyDB | Google Cloud | PostgreSQL 兼容 | 列式引擎加速分析 |
自建 vs 托管决策指南#
| 自建适用场景 | 托管适用场景 |
|---|---|
| 需要特定扩展或定制补丁 | 标准配置即可满足需求 |
| 极致性能调优要求 | 运维简洁性优先 |
| 超大规模下成本敏感 | 团队精简,无专职 DBA |
| 合规要求完全掌控基础设施 | 云厂商满足合规认证 |
| 教学/学习目的 | 生产级工作负载 |
容量规划(Capacity Planning)#
垂直扩展(Scale Up) vs 水平扩展(Scale Out)#
| |
容量估算示例#
| |
零停机 Schema 迁移#
常规迁移锁表并阻塞查询。在大规模场景下,对十亿行表加锁 30 秒就会导致级联超时。零停机迁移在数据库服务实时流量时重构结构。
扩展-收缩模式#
不做一步破坏性迁移,而用三个安全步骤:
| |
示例:重命名列#
| |
在线 DDL 对比#
| 操作 | PostgreSQL | MySQL (InnoDB) |
|---|---|---|
| 加列(无默认值) | 即时 | 即时(8.0+) |
| 加列(有默认值) | 即时(11+) | 复制表 |
| 加索引 | CONCURRENTLY(不锁表) | ALGORITHM=INPLACE |
| 删列 | 快速 | 复制表 |
| 改列类型 | 复制表(用扩展-收缩) | 复制表 |
安全迁移工具#
| |
可观测性:数据库仪表板#
看不到的东西无法优化。生产数据库需要覆盖以下指标的实时仪表板:
数据库四大黄金信号#
| 信号 | 指标 | 告警阈值 |
|---|---|---|
| 延迟 | p50/p95/p99 查询时间 | p99 > 500ms |
| 流量 | QPS(每秒查询数) | 突然下降 50% 或飙升 3 倍 |
| 错误 | 失败查询、死锁、连接拒绝 | 任何死锁;错误率 > 1% |
| 饱和度 | 连接池使用率、磁盘 IO、CPU | 连接池 > 80%;磁盘 IO > 70% |
PostgreSQL 关键监控指标#
| |
连接池调优#
| |
| |
告警规则(Prometheus/Grafana)#
| |
故障案例(War Stories):常见生产事故#
| 事故 | 根因 | 表象 | 修复 | 预防 |
|---|---|---|---|---|
| 死亡查询 | 新功能缺失索引 | CPU 100%,所有查询变慢 | CREATE INDEX CONCURRENTLY | 代码审查强制要求新查询附带 EXPLAIN |
| 连接耗尽 | 无连接池,应用扩至 50 个 Pod | “too many connections” 报错 | 加 PgBouncer,降低每 Pod 连接池大小 | 始终启用连接池 |
| 复制中断 | 主库 DDL 未设 SET STATEMENT_FORMAT=ROW | 从库数据漂移、读取陈旧 | 重新拉取从库快照 | 统一 binlog 格式为 ROW |
| 磁盘满 | 闲置复制槽(replication slot)导致 WAL 无限增长 | 数据库拒绝写入 | SELECT pg_drop_replication_slot('dead_slot') | 监控 pg_replication_slots,对非活跃槽告警 |
| 表膨胀 | Autovacuum 无法跟上高频 UPDATE 表 | 查询逐次变慢 | 手动 VACUUM FULL(锁表) | 按表调优 autovacuum_vacuum_scale_factor |
| 锁队列 | 长迁移持有排他锁 | 所有查询排队等待 | 杀掉迁移,重试并设 lock_timeout | 迁移设置 lock_timeout = '5s' |
| OOM Killer | 内存中巨型排序(未限制 work_mem) | PostgreSQL 进程被杀,连接断开 | 设 work_mem = '256MB',加索引 | 配置 work_mem 与 temp_file_limit |
| 级联故障 | DB 变慢 → 应用重试 ×3 → 负载 ×3 → DB 更慢 | 全站宕机 | 熔断器介入,终止重试 | 实现熔断器,设置查询超时 |
| 数据损坏 | 为“性能”关闭 fsync=off | 断电后静默丢数据 | 从备份恢复 | 生产环境永不关闭 fsync |
| 迁移失败 | 5 亿行表上 ALTER TABLE 超时 | 锁卡住,写入阻塞 20 分钟 | 改用 gh-ost / pt-online-schema-change | 大表迁移必须用在线 DDL 工具 |
黄金守则(The Golden Rules)#
永远有备份,并验证恢复流程。 从未恢复过的备份,只是幻想,不是策略。
监控先行: 上线首个生产用户前,就应建好仪表盘与告警。关键指标:连接数、查询延迟 p99、复制延迟、磁盘使用率。
始终启用连接池: 即使你的框架声称“内置连接池”,也请独立部署专业连接池(如 PgBouncer / ProxySQL)。
处处设超时: 语句超时、锁超时、连接超时、空闲事务超时。没有超时的查询,终将永久持有锁。
| |
绝不未经测试就在生产执行迁移。 每次迁移必须在生产数据副本上实测:耗时多少?是否加锁?影响范围?
只读副本不能解决写瓶颈: 它们只加速读请求。若写是瓶颈,你需要分片(sharding)。
事务务必短小: 一个持有锁 30 秒的事务,会阻塞所有后续需要这些行的事务。
数据库不是消息队列: 如果你在轮询
SELECT ... WHERE status = 'pending' FOR UPDATE SKIP LOCKED,你应该用真正的消息队列(如 Kafka、RabbitMQ)。
系列结语#
八篇文章,我们从关系模型与 SQL 基础出发,一路抵达分布式事务与生产运维实践。这条路径并非偶然:你不理解隔离级别,就无法明白为何复制延迟如此关键;你不理解两阶段提交(2PC)的阻塞本质,就无法真正欣赏 Saga 模式的精妙。
数据库正是这样一个领域:浅层认知极其危险。不了解索引的开发者,会构建出在小数据集上完美运行、却在生产中彻底崩塌的系统;不了解隔离级别的团队,会发布只有在高并发下才暴露的竞态 Bug;不验证备份的组织,会在最需要它时发现备份毫无价值。
底层原理变化甚微:B-tree、WAL、MVCC、共识算法,数十年来始终是核心基石。掌握它们一次,你面对 PostgreSQL、CockroachDB、DynamoDB,乃至未来任何新数据库,看到的都只是你早已理解思想的不同变体。