在这里插入图片描述

上一篇我们讲了单库单表的瓶颈——当数据量达到千万级、并发突破千级时,查询慢、写入卡、甚至锁表的问题会集中爆发。而分库分表的核心思路,就是通过“拆分”把压力分散到多个库/表中。

但拆分不是拍脑袋决定的:哪些场景该按“字段”拆?哪些该按“行”拆?拆完后能解决什么问题?又会引入哪些新麻烦?

今天这篇文章,我们就深入拆解“垂直拆分”和“水平拆分”的具体玩法——包括垂直分表/分库、水平分表/分库的适用场景、优缺点、实操案例(附SQL),帮你搞懂“怎么拆才合理”。

一、垂直拆分:按“业务/字段”做“减法”

垂直拆分的核心逻辑是“按数据的‘属性’拆分”——要么按“业务域”(如用户、订单、商品)拆到不同库,要么按“字段访问频率”(核心字段、扩展字段)拆到不同表。目标是让每个库/表的“职责更单一”,避免“大而全”导致的性能问题。

1. 垂直分表:解决“单表字段太多,大字段拖慢查询”

什么是垂直分表?

把单表中“访问频率差异大”或“字段体积差异大”的列拆分到不同表中,主表保留核心高频字段,扩展表存低频或大字段。

典型场景
用户表(user)有20个字段,其中id(用户ID)、username(用户名)、phone(手机号)是高频查询字段(如登录、个人中心展示);而avatar(头像URL,长字符串)、introduction(个人简介,TEXT类型)、reg_ip(注册IP)是低频查询字段(如查看详细资料时才用)。

如果不拆分,查询“用户登录信息”时,会把avatar这类大字段也加载到内存,不仅浪费IO(读取更多数据),还占用缓存(MySQL的Buffer Pool),导致核心字段的缓存命中率下降。

实操案例:用户表垂直分表

拆分前:单表user包含所有字段

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL PRIMARY KEY,
  `username` varchar(50) NOT NULL,
  `phone` varchar(20) NOT NULL,
  `password` varchar(100) NOT NULL, -- 加密存储,高频使用(登录验证)
  `avatar` varchar(255) DEFAULT NULL, -- 大字段,低频访问
  `introduction` text DEFAULT NULL, -- 超大字段,极少访问
  `reg_time` datetime NOT NULL,
  `reg_ip` varchar(50) DEFAULT NULL -- 低频访问
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

拆分后:拆分为user_core(核心高频字段)和user_extra(扩展低频字段)

-- 核心表:存储高频访问字段,查询效率高
CREATE TABLE `user_core` (
  `id` bigint(20) NOT NULL PRIMARY KEY,
  `username` varchar(50) NOT NULL,
  `phone` varchar(20) NOT NULL,
  `password` varchar(100) NOT NULL,
  `reg_time` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 扩展表:存储低频/大字段,仅在需要时查询
CREATE TABLE `user_extra` (
  `user_id` bigint(20) NOT NULL PRIMARY KEY, -- 与user_core.id关联
  `avatar` varchar(255) DEFAULT NULL,
  `introduction` text DEFAULT NULL,
  `reg_ip` varchar(50) DEFAULT NULL,
  CONSTRAINT `fk_user_extra_id` FOREIGN KEY (`user_id`) REFERENCES `user_core` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

查询优化

  • 登录场景(只需核心字段):直接查user_core,避免加载大字段
    SELECT id, username, password FROM user_core WHERE phone = '13800138000';
    
  • 查看详细资料(需扩展字段):关联查询(仅在必要时执行)
    SELECT c.id, c.username, e.avatar, e.introduction 
    FROM user_core c 
    LEFT JOIN user_extra e ON c.id = e.user_id 
    WHERE c.id = 1001;
    
优点与缺点
优点 缺点
1. 减少单表字段数,查询时IO更少(一页能加载更多行);
2. 核心字段缓存命中率更高(节省内存);
3. 大字段(如TEXT)单独存储,避免拖慢全表查询。
1. 需关联查询(JOIN),增加代码复杂度;
2. 事务操作需同时处理两张表(如创建用户时需插入core和extra)。
适用场景判断

当你的表符合以下特征时,优先考虑垂直分表:

  • 单表字段数过多(如超过20个);
  • 存在大字段(如TEXT、长VARCHAR)且访问频率低;
  • 核心查询(如列表、详情)只用到部分字段,大部分字段很少被查询。

2. 垂直分库:解决“单库职责太重,业务互相影响”

什么是垂直分库?

按“业务域”将不同表拆分到独立的数据库实例中,实现“业务隔离”。每个库只负责一个或一类业务,避免“一个业务的压力拖垮全库”。

典型场景
电商系统初期,所有表(用户表、订单表、商品表、库存表)都存在一个数据库(ecommerce_db)中。当订单业务搞促销(并发1000+)时,大量的订单写入会占用数据库连接和IO,导致用户登录(查用户表)、商品浏览(查商品表)也变得卡顿——这就是“业务耦合”导致的连锁反应。

实操案例:电商系统垂直分库

拆分前:单库ecommerce_db包含所有业务表

ecommerce_db/  
├─ user(用户表)  
├─ order(订单表)  
├─ order_item(订单项表)  
├─ goods(商品表)  
├─ stock(库存表)  
└─ address(地址表)  

拆分后:按业务拆分为3个独立库

user_db/(用户相关)  
├─ user(用户表)  
└─ address(地址表)  

order_db/(订单相关)  
├─ order(订单表)  
└─ order_item(订单项表)  

goods_db/(商品相关)  
├─ goods(商品表)  
└─ stock(库存表)  

效果

  • 订单促销时,order_db的压力不会影响user_db(用户登录正常)和goods_db(商品浏览正常);
  • 各库可独立扩容(如order_db用更高配置的服务器);
  • 业务逻辑更清晰(开发订单功能的同学只需关注order_db)。
优点与缺点
优点 缺点
1. 业务隔离,避免单库故障影响全系统;
2. 各库可按需独立扩容(如订单库配SSD,用户库配大内存);
3. 团队协作更高效(不同业务团队负责不同库)。
1. 跨库事务难处理(如创建订单需扣库存,涉及order_db和goods_db);
2. 跨库查询复杂(如“查询用户的订单及商品信息”需查3个库)。
适用场景判断

当你的系统符合以下特征时,优先考虑垂直分库:

  • 业务模块清晰(如用户、订单、商品可明确拆分);
  • 不同业务的访问频率和压力差异大(如订单库并发高,用户库读写均衡);
  • 单库已出现“某类业务拖垮全库”的情况。

二、水平拆分:按“数据行”做“除法”

水平拆分的核心逻辑是“按数据的‘行特征’拆分”——把单表的行数据按规则分到多个表(甚至多个库),每个子表的结构完全相同,但数据互补(合起来是完整数据)。目标是“减少单表数据量”,突破千万/亿级数据的性能瓶颈。

1. 水平分表:解决“单表数据量过大,查询变慢”

什么是水平分表?

将单表中大量的行数据,按一定规则(如时间、ID哈希)拆分到多个结构相同的子表中。比如1亿行的订单表,按月份拆成12个表,每个表只有约800万行。

典型场景
订单表(order)随着业务增长,数据量达到2亿行,即使加了索引,查询“用户近3个月订单”也需要5秒(全表扫描部分数据),分页查询更是卡顿。此时单表的索引效率骤降,磁盘IO压力大,必须通过水平分表减少单表数据量。

拆分规则与实操案例

水平分表的关键是“拆分规则”,需保证数据均匀分布(避免某张子表过大),且符合业务查询习惯(避免频繁跨表查询)。常见规则有3种:

(1)范围拆分:按时间/ID区间拆分(最常用)

规则:按时间(如月份、季度)或ID区间(如1-100万、101-200万)拆分,适合“有明显时间/ID递增特征”的表(如订单表、日志表)。

案例:订单表按月份分表
拆分前:单表order存储所有订单

CREATE TABLE `order` (
  `id` bigint(20) NOT NULL PRIMARY KEY,
  `order_no` varchar(50) NOT NULL,
  `user_id` bigint(20) NOT NULL,
  `amount` decimal(10,2) NOT NULL,
  `create_time` datetime NOT NULL, -- 按该字段分表
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

拆分后:按create_time拆分为order_202310order_202311order_202312……(每月一张表)

-- 2023年10月订单表
CREATE TABLE `order_202310` (
  `id` bigint(20) NOT NULL PRIMARY KEY,
  `order_no` varchar(50) NOT NULL,
  `user_id` bigint(20) NOT NULL,
  `amount` decimal(10,2) NOT NULL,
  `create_time` datetime NOT NULL,
  KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 2023年11月订单表(结构同上)
CREATE TABLE `order_202311` (...);

查询优化

  • 查“2023年10月的订单”:直接查order_202310,单表数据量小,查询快
    SELECT * FROM order_202310 WHERE user_id = 1001;
    
  • 查“2023年10-11月的订单”:需查两张表并聚合结果(可通过中间件自动处理)
    -- 手动查询时需union
    SELECT * FROM order_202310 WHERE user_id = 1001
    UNION ALL
    SELECT * FROM order_202311 WHERE user_id = 1001;
    
(2)哈希拆分:按关键字哈希取模(均衡性最好)

规则:按关键字(如用户ID、订单ID)的哈希值取模(如模8),将数据分到8张子表,适合“查询分散,无明显时间特征”的表(如用户表、商品表)。

案例:用户表按ID哈希分表
拆分规则:user_id % 4(拆成4张表,user_0user_3

-- user_id %4 =0 的用户存这里
CREATE TABLE `user_0` (
  `id` bigint(20) NOT NULL PRIMARY KEY, -- user_id
  `username` varchar(50) NOT NULL,
  `phone` varchar(20) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- user_id %4 =1 的用户存这里(结构同上)
CREATE TABLE `user_1` (...);
-- user_2、user_3 类似

查询优化

  • 查“user_id=1001的用户”:先计算1001%4=1,直接查user_1
    SELECT * FROM user_1 WHERE id = 1001;
    
(3)列表拆分:按固定枚举值拆分(业务相关性强)

规则:按业务枚举值(如地区、状态)拆分,适合“查询常按枚举值过滤”的表(如地区订单表、状态区分的任务表)。

案例:订单表按地区分表
拆分规则:按用户所在地区(北京、上海、广州、其他)拆分为4张表

CREATE TABLE `order_beijing` (...); -- 北京用户的订单
CREATE TABLE `order_shanghai` (...); -- 上海用户的订单
CREATE TABLE `order_guangzhou` (...); -- 广州用户的订单
CREATE TABLE `order_other` (...); -- 其他地区用户的订单
优点与缺点
优点 缺点
1. 单表数据量大幅减少(从亿级到千万/百万级),查询/写入性能显著提升;
2. 可按需扩容(如新增月份表、新增哈希分表);
3. 适合高并发写入场景(数据分散,锁竞争少)。
1. 跨表查询复杂(如查“所有表的前10条数据”需聚合);
2. 分页查询麻烦(如“查第100页数据”需从所有表取数再排序);
3. 拆分规则设计难(需保证数据均匀,避免热点表)。
适用场景判断

当你的表符合以下特征时,优先考虑水平分表:

  • 单表数据量超过千万级(MySQL单表建议阈值:500万-2000万行);
  • 查询以“单表过滤”为主(如按时间、用户ID查询),跨表查询少;
  • 写入频繁(如订单表、日志表),单表写入性能不足。

2. 水平分库:解决“单库并发过高,资源瓶颈”

什么是水平分库?

在水平分表的基础上,将子表分散到多个数据库实例中。比如按用户ID哈希分8张表,再把这8张表分到2个库(每个库4张表),既分散数据量,又分散并发压力。

典型场景
电商秒杀场景,订单表已水平分8张表,但所有表仍在一个库中。秒杀时每秒1万次写入,单库的CPU、内存、IO全满,连接数爆表(超过1000),此时必须通过水平分库分散数据库实例的压力。

实操案例:订单表水平分库分表

拆分规则:先按“用户ID哈希%2”分2个库(order_db_0order_db_1),再在每个库内按“用户ID哈希%4”分4张表(order_0order_3)。

order_db_0/(用户ID%2=0)  
├─ order_0(用户ID%4=0)  
├─ order_1(用户ID%4=1)  
├─ order_2(用户ID%4=2)  
└─ order_3(用户ID%4=3)  

order_db_1/(用户ID%2=1)  
├─ order_0(用户ID%4=0)  
├─ order_1(用户ID%4=1)  
├─ order_2(用户ID%4=2)  
└─ order_3(用户ID%4=3)  

路由逻辑
查询“user_id=1001的订单”:

  1. 计算1001%2=1 → 路由到order_db_1
  2. 计算1001%4=1 → 路由到order_db_1中的order_1表;
  3. 最终查询:select * from order_db_1.order_1 where user_id=1001
优点与缺点
优点 缺点
1. 分散单库并发压力(连接数、CPU、IO分散到多个实例);
2. 存储容量扩展(多库存储,突破单库磁盘限制);
3. 结合水平分表,性能提升更明显。
1. 分布式事务难处理(跨库操作需保证一致性);
2. 路由逻辑复杂(需同时判断库和表);
3. 运维成本高(多库多表,备份、迁移更麻烦)。
适用场景判断

当你的系统符合以下特征时,考虑水平分库:

  • 水平分表后,单库并发仍过高(如每秒写入超5000次,连接数常超800);
  • 单库存储容量不足(如单库数据量超1TB,磁盘空间紧张);
  • 有异地部署需求(如北京库+上海库,就近访问)。

三、垂直拆分vs水平拆分:核心差异与决策指南

四种拆分方式的核心差异

拆分方式 拆分维度 解决的核心问题 数据分布特征 复杂度
垂直分表 字段(列) 单表字段过多,大字段拖慢查询 表结构不同,数据互补
垂直分库 业务域(表) 单库职责过重,业务互相影响 库内表独立,业务隔离
水平分表 数据行 单表数据量过大,查询慢 表结构相同,数据互补
水平分库 数据行+数据库 单库并发过高,资源瓶颈 库和表结构都相同,数据互补

决策指南:先垂直,后水平;先分表,后分库

  1. 优先垂直拆分
    垂直拆分是“业务驱动”的拆分,先通过垂直分库实现业务隔离(如用户、订单、商品库分离),再通过垂直分表优化单表字段(如核心字段与扩展字段分离)。这一步能解决80%的“业务耦合”问题,且复杂度低。

  2. 再考虑水平拆分
    当垂直拆分后,单表数据量仍过大(千万级以上)或单库并发过高(超千级),再进行水平拆分。水平分表优先解决“单表性能”,水平分库再解决“单库资源”瓶颈。

  3. 避免过度拆分
    拆分是有成本的(跨库跨表查询、分布式事务),小系统(数据量百万级以下)无需拆分;即使需要拆分,也应“按需拆分”(如只拆分订单表,用户表保持单表)。

总结:拆分的本质是“取舍”

垂直拆分和水平拆分没有绝对的优劣,核心是“根据业务场景取舍”:

  • 垂直拆分取“业务清晰、隔离性好”,舍“跨库跨表复杂度”;
  • 水平拆分取“数据量分散、性能提升”,舍“查询和事务复杂度”。

下一篇文章,我们将讲解分库分表的“实现层面”——手动编码、中间件(Sharding-JDBC/MyCat)、分布式数据库该怎么选,附具体工具的配置案例。

(觉得有用的话,欢迎点赞收藏,关注下一篇~)

Logo

更多推荐