前两篇我们解决了”单条 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 配合池、别迷信查询缓存。
数据库调优三篇到这。但调优的前提是表设计得好——如果一开始建模就乱,再怎么调也救不回来。下个系列我们回到源头:数据建模。