教程
🗄️

数据库

MySQL、Redis、MongoDB 等数据库原理与调优。

架构级调优:读写分离、分库分表与配置

当单库扛不住时,从架构层面破局——读写分离分散压力、分库分表突破容量上限,以及连接池与关键参数的配置要点。

· 更新于 2026-07-20 阅读量 --

前两篇我们解决了”单条 SQL 怎么快”。但有时候,SQL 已经写得很好、索引也建了,数据库还是扛不住——因为单台机器的 CPU、内存、磁盘 IO 是有物理上限的。这时要从架构层面破局。

这一篇讲三种架构级手段:读写分离、分库分表、以及配置与连接池调优。

一、读写分离:把读压力分流出去

绝大多数业务读多写少(刷列表、看详情都是读,下单才是写)。一台主库既要扛写又要扛所有读,很容易到顶。解法:一主多从

  • 主库(Master):负责写(INSERT/UPDATE/DELETE),并把数据变更通过binlog同步给从库。
  • 从库(Slave):负责读(SELECT),可以挂多个,水平扩展读能力。

应用层把”写请求发主库、读请求发从库”。读流量被多个从库分担,主库压力骤减。

注意主从延迟:数据写主库后,同步到从库有毫秒到秒级延迟。刚下单立刻查(读从库)可能查不到——这就是”读己之写”问题。解法:关键读(如刚下单看订单)走主库,或等几秒/强制读主。

二、分库分表:突破单表容量上限

当单表涨到千万甚至上亿行,即使有索引,B+ 树变高、维护变慢、备份恢复都难。这时要”拆”:

垂直拆分

  • 垂直分库:按业务把表分到不同库,比如用户库、订单库、商品库,分散到不同机器。
  • 垂直分表:把一张宽表按”冷热字段”拆开,比如用户表拆成 user_base(常用:id/name)和 user_profile(少用:简介/偏好),避免大字段拖慢常用查询。

水平拆分(分表)

把**同一张表的数据按某个分片键(sharding key)**拆到多个物理表/库。比如按 user_id 取模:

user_id % 4 == 0 → 表 orders_0
user_id % 4 == 1 → 表 orders_1
user_id % 4 == 2 → 表 orders_2
user_id % 4 == 3 → 表 orders_3

这样每张表只有总量的 1/4,查询和写入都变轻。分片键的选择是核心:要选查询最常用、分布均匀的列(如 user_id)。选错(如按”性别”分)会导致数据严重倾斜、某片爆满。

水平拆分代价大:跨分片 JOIN、跨分片聚合、全局唯一 ID、分布式事务都变复杂。所以能不拆就不拆,先靠读写分离和索引顶住,真到瓶颈再拆。

三、连接池与配置调优

很多”数据库慢”其实是连接管理问题:

  • 连接池:每次新建数据库连接很贵(TCP 握手+认证)。用连接池(如 HikariCP、Druid)复用连接。关键参数:
    • maxPoolSize:最大连接数。太小并发上不去,太大压垮数据库(数据库能承受的连接数有限,一般几十到几百)。
    • minIdle:最小空闲连接,避免冷启动建连延迟。
  • 避免长事务:一个事务里干太多事(还顺便调了外部接口),连接长时间占着不释放,池被耗尽,其他请求全阻塞。事务要”短平快”。

几个关键数据库参数(MySQL 为例)

  • innodb_buffer_pool_size:InnoDB 缓存数据和索引的内存大小,越大越能命中内存、少读磁盘,通常设为机器内存的 60–70%。这是 MySQL 调优第一参数。
  • max_connections:最大连接数,要和连接池上限配合,别让应用把数据库连接打满。
  • query_cache:新版 MySQL 已移除查询缓存(并发下弊大于利),别迷信它。

常见坑位提醒

  • 读写分离忽略主从延迟:刚写就读从库查不到,关键读要路由主库。
  • 过早分库分表:分片后跨片查询、分布式事务复杂度飙升。先读写分离+索引,真到亿级再拆。
  • 分片键选错:按低区分度/倾斜字段分片,数据分布不均,部分分片成为热点瓶颈。
  • 连接池设太大:以为”大就是好”,结果把数据库最大连接打满,全站雪崩。连接数要算总账(应用实例数 × 单实例池大小 ≤ 数据库上限)。
  • buffer pool 设太小:默认往往很小,内存没利用起来,全靠磁盘 IO,慢得离谱。
  • 长事务占连接:事务里做耗时操作(HTTP 调用、大循环),连接长期不释放,池耗尽。

分片键进阶:一致性哈希

前面用 user_id % 4 取模分片,简单但有缺陷:扩容时要重新取模、几乎全量迁移数据(从 4 片扩到 5 片,几乎所有数据位置都变)。一致性哈希是解决思路:把”分片节点”和”数据 key”都映射到一个环形空间,数据顺时针找最近的节点。扩容时只迁移环上相邻一小段数据,影响面小得多。很多中间件(如 Redis Cluster、Cassandra)内部就用一致性哈希变体。

中间件:把复杂度藏起来

分库分表、读写分离很复杂,业务代码不该直接操心”该查哪片”。用中间件代理:

  • ShardingSphere / MyCat:在应用和数据库之间做分片路由、读写分离,对应用透明,SQL 照写,路由由中间件完成。
  • ProxySQL / MySQL Router:专门做读写分离和连接池代理,自动把写发主库、读发从库。
  • Vitess:大规模(如 YouTube 量级)的 MySQL 分片方案。

用中间件能让你”逻辑上像单库、物理上已分布”,大幅降低开发心智负担。但中间件本身也是要运维的组件,小团队先用云数据库自带的读写分离/只读实例更省事。

缓存层架构:Redis 不是只有”查库前挡一下”

除了”先查 Redis 再查 MySQL”的基础缓存,还有两种进阶用法:

  • 缓存预热:系统启动或大促前,主动把热点数据(如首页商品)提前加载进 Redis,避免冷启动瞬间全打数据库。
  • 多级缓存:本地缓存(如 Caffeine,进程内,纳秒级)+ 分布式缓存(Redis,微秒级)+ 数据库。读请求先查本地、miss 再查 Redis、再 miss 才查库,层层兜底,扛住极端流量。
  • CDN 缓存:静态资源(图片、JS)推到边缘节点,连 Redis 都不用经过。

调优的本质是”让请求在离用户最近、最快的地方被满足”,缓存层级越靠前,数据库压力越小。

参数调优实战案例

一个真实场景:某接口偶发超时,排查发现 innodb_buffer_pool_size 只有默认的 128MB,而表数据有 20GB,意味着绝大部分读都要落磁盘。把它调到机器内存的 65%(约 13GB)后,缓冲命中率从 40% 升到 98%,接口 P99 从 2 秒降到 80 毫秒。类似的还有:

  • innodb_log_file_size:调大减少刷盘频率,写多场景受益。
  • thread_cache_size / table_open_cache:减少连接和开表的重复开销。
  • 连接池 maxPoolSize 按”应用实例数 × 单实例池大小 ≤ 数据库 max_connections”反推,宁小勿大。

调参不是玄学,靠监控指标驱动:盯住缓冲命中率、连接数、慢查询数、磁盘 IO,哪里红修哪里。

小测验:看看你掌握了没

  • 问题一:读写分离解决什么问题?答案:把读压力从主库分流到多个从库,主库专注写,突破单机读瓶颈。
  • 问题二:分片键为什么重要?答案:决定数据分布,应选高频且分布均匀的列,选错会数据倾斜成热点。
  • 问题三:连接池是不是越大越好?答案:不是,过大会打满数据库最大连接,引发全站雪崩,要按总账计算。

这一篇你该记住的

  • 单库到顶要从架构破局:读写分离、分库分表、配置/连接池调优。
  • 读写分离:主写从读,靠 binlog 同步;注意主从延迟,关键读走主库。
  • 分库分表:垂直(按业务/冷热)与水平(按分片键取模);分片键选高频均匀列;能不拆不拆。
  • 连接池复用连接,大小要算总账;事务要短,避免长事务占连接。
  • 关键参数:buffer pool 调大、max_connections 配合池、别迷信查询缓存。

数据库调优三篇到这。但调优的前提是表设计得好——如果一开始建模就乱,再怎么调也救不回来。下个系列我们回到源头:数据建模