ARTICLE / 3 MIN READ

数据库在高并发下的连接池、读写分离与分片

从慢查询和连接耗尽出发,给出关系型数据库逐级扩展的判断标准与迁移步骤。

很多系统在数据库变慢后直接分库分表,结果把事务、查询和运维复杂度一起放大。更稳妥的顺序是先消除无效查询,再扩大读能力,最后才拆分数据所有权。

先处理查询本身

慢查询排查要同时看执行计划、扫描行数、返回行数、锁等待和排序临时表。一个高频列表接口应使用覆盖索引和明确分页,避免 SELECT * 和深分页。

SELECT id, status, created_at
FROM orders
WHERE tenant_id = ?
  AND created_at < ?
ORDER BY created_at DESC, id DESC
LIMIT 50;

使用游标分页比 OFFSET 更稳定,尤其是数据量达到千万级以后。

连接池不是越大越好

连接数过小会在应用层排队,过大则会让数据库上下文切换、内存和锁竞争上升。应用应监控连接获取等待时间、活跃连接、空闲连接和超时释放数量。

连接池的上限需要用压测找出。每个实例的池大小还要乘以实例数,最终与数据库可接受连接数一起核算。

读写分离的延迟

读写分离适合“写入后允许短时间读旧值”的场景。用户刚创建订单后,查询详情应优先读主库或携带写入时间戳,避免因复制延迟显示不存在。

读副本必须有健康检查和延迟阈值。副本延迟超过阈值时,应自动摘流或降级,而不是继续把读流量打到已经落后的节点。

什么时候需要分片

当单库容量、写入吞吐或索引维护达到硬上限,并且垂直拆表和归档都不能解决时,再考虑分片。分片键应满足:

  • 查询大多数带上该字段;
  • 数据分布均匀,避免热点租户;
  • 业务生命周期内不会频繁变更;
  • 能够定位事务边界。

跨分片 JOIN 和全局排序会显著增加成本,通常要在应用层聚合,或把查询模型同步到搜索和分析存储。

一次可回滚的迁移

  1. 增加新表和双写字段,不改变旧读路径。
  2. 通过消息或 CDC 回填历史数据。
  3. 对账行数、金额、状态和更新时间。
  4. 灰度切换读流量,保留旧路径。
  5. 稳定后再停止双写并归档旧表。

数据库扩展的核心是保持业务可验证。每一步都有对账、监控和回滚点,才不会因为一次“优化”把数据正确性变成未知数。

GITHUB DISCUSSION

评论与回复

评论保存在 GitHub Discussions,登录 GitHub 后即可参与,发布和回复都在本页完成。

评论区进入视口后自动加载。