SQL 水平拆分与垂直拆分的取舍

9次阅读

水平拆分适用于单表数据量达千万至亿级导致查询慢、主从延迟高、备份恢复慢等场景,需自行处理路由、聚合、全局统计及分布式ID;垂直拆分则按列分离冷热数据或更新频率差异大的字段,降低I/O与锁竞争,不改变查询逻辑;混合拆分常见但复杂度高,须统一元数据管理。

SQL 水平拆分与垂直拆分的取舍

水平拆分适合单表数据量爆炸,但得自己管路由和聚合

当一张表超过千万甚至上亿行,查询变慢、主从延迟拉高、备份恢复耗时,这时候水平拆分(Sharding)才真正有用。它把同一张表的数据按某种规则(比如 user_id % 4)分散到多个物理库/表中,每份只存一部分数据。

但代价明显:你得在应用层或中间件里实现分片键识别、sql 路由、跨分片 JOINGROUP BY 的结果合并;select count(*) 这种全局统计必须改写成多节点并发查再汇总;自增主键基本废掉,得换 UUID 或雪花算法

常见踩坑点:

  • 误把高频关联字段(如 order.user_iduser.id)设成不同分片键,导致跨库 JOIN 不可避免
  • 用时间范围做分片(如按月建表),但业务查的是“最近 7 天订单”,结果要扫 2–3 张表,反而更慢
  • 没预留扩容能力,初始分 4 片,等流量翻倍后发现没法平滑加到 8 片,只能停机迁移

垂直拆分解决耦合与冷热分离,但别拆过头

垂直拆分是把一张宽表按列拆成多张逻辑相关的表,比如把 user 表拆成 user_base(登录名、密码)、user_profile(昵称、头像)、user_stats(积分、等级)。目标是降低单表宽度、减少 I/O、隔离读写压力、方便按需扩缩容。

它不碰行数,所以不用改查询逻辑,也不影响主键和事务。但拆得不合理会引入大量关联查询,反而加重数据库负担。

实用建议:

  • 优先拆“更新频率差异大”的字段,比如用户密码很少改,但积分每秒都在变,放一起会导致整行锁竞争
  • 把大字段(TEXTBLOB)单独拎出来,避免日常 SELECT * 拖慢所有查询
  • 别为“看着整洁”而拆——例如把 created_atupdated_at 单独建一张表,毫无意义

先垂直,再水平;能不拆,就不拆

90% 的性能问题其实出在索引缺失、慢 SQL、连接池配置或硬件瓶颈,而不是数据量本身。上线前先做压测,确认单库单表真扛不住了,再考虑拆分。

拆分顺序有现实约束:

  • 垂直拆分成本低、风险小,能立刻缓解部分压力,适合作为第一阶段动作
  • 水平拆分是“不可逆操作”,一旦开始就绑定分片逻辑,后续所有新功能都要考虑分片语义
  • 很多团队卡在“想水平但不敢动”,最后用读写分离 + 缓存 + 归档老数据撑了两年,发现根本没到非拆不可的地步

混合拆分常见但容易失控,必须有统一元数据管理

真实系统往往是混合模式:用户中心垂直拆成几张表,其中 user_order 又按 user_id 水平拆了 8 个库。这种组合放大了复杂度——你既要维护字段归属关系,又要管理分片映射、跨库事务边界、分布式唯一 ID 生成策略。

最容易被忽略的点是元数据同步:

  • 新增一个字段,得同步改多个库的 DDL,漏一个就会导致应用报 Unknown column
  • 某个分片库升级了 mysql 版本,其他没跟上,结果 jsON_EXTRACT 行为不一致,查出来空值
  • 没有集中式分片配置中心,靠配置文件编码分片规则,上线新节点时手动改代码,出错率飙升

真正难的从来不是怎么拆,而是拆完之后,怎么让所有人(开发、dba、运维)对“这张表现在在哪、怎么查、哪些字段属于哪一层”保持一致认知。

text=ZqhQzanResources