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 和全局排序会显著增加成本,通常要在应用层聚合,或把查询模型同步到搜索和分析存储。
一次可回滚的迁移
- 增加新表和双写字段,不改变旧读路径。
- 通过消息或 CDC 回填历史数据。
- 对账行数、金额、状态和更新时间。
- 灰度切换读流量,保留旧路径。
- 稳定后再停止双写并归档旧表。
数据库扩展的核心是保持业务可验证。每一步都有对账、监控和回滚点,才不会因为一次“优化”把数据正确性变成未知数。
GITHUB DISCUSSION
评论与回复
评论保存在 GitHub Discussions,登录 GitHub 后即可参与,发布和回复都在本页完成。
评论区进入视口后自动加载。