表字段过多时的垂直拆分策略
什么是垂直拆分
垂直拆分(Vertical Partitioning / Vertical Sharding)是指将一张列数过多的表,按列拆分为多张结构不同的表,每张表拥有原表的部分列。
这与水平拆分(按行拆分)不同,垂直拆分是按列划分。
什么时候需要垂直拆分
MySQL 的一些限制让垂直拆分成为必要:
| 限制项 | 具体限制 | 影响 |
|---|---|---|
| 最大列数 | InnoDB 最多 1017 列 | 极端场景 |
| 行大小限制 | 65535 字节(溢出页除外) | 大字段多时很严重 |
| 页大小 | 默认 16KB,单行不能超过半页 | 实际 8000 字节左右 |
| Buffer Pool 效率 | 宽表减少每页的行数 | 降低缓存效率 |
业务信号:
– 一张表超过 30-50 个字段
– 业务上明显有几组字段使用频率差异很大(热字段 vs 冷字段)
– 大字段(TEXT/BLOB)和小字段混在一起
– 某些字段极少被查询
垂直拆分的常见模式
1. 基础信息 + 扩展信息
最常见的拆分:热点字段分离。
-- 原表(30+ 字段)
CREATE TABLE user (
id INT PRIMARY KEY,
username VARCHAR(50), -- 常用
email VARCHAR(100), -- 常用
password_hash CHAR(60), -- 登录时用
avatar_url VARCHAR(200), -- 常用
nickname VARCHAR(50), -- 常用
...
registration_ip VARCHAR(45), -- 极少用
last_login_ip VARCHAR(45), -- 极少用
referrer VARCHAR(200), -- 极少用
remark TEXT, -- 极少用
internal_notes TEXT -- 极少用
);
-- 拆分后
CREATE TABLE user_base (
id INT PRIMARY KEY,
username VARCHAR(50),
email VARCHAR(100),
password_hash CHAR(60),
avatar_url VARCHAR(200),
nickname VARCHAR(50)
); -- 热点字段,频繁查询
CREATE TABLE user_ext (
user_id INT PRIMARY KEY,
registration_ip VARCHAR(45),
last_login_ip VARCHAR(45),
referrer VARCHAR(200),
remark TEXT,
internal_notes TEXT
); -- 冷数据,极少访问
2. 业务模块拆分
某些字段在业务上属于不同模块:
-- 订单表拆分为:订单基础 + 支付 + 物流
CREATE TABLE order_base (
id INT PRIMARY KEY,
user_id INT,
total_amount DECIMAL(12,2),
status TINYINT,
created_at DATETIME
);
CREATE TABLE order_payment (
order_id INT PRIMARY KEY,
payment_method VARCHAR(20),
payment_status TINYINT,
paid_at DATETIME,
transaction_id VARCHAR(100),
refund_reason TEXT
);
CREATE TABLE order_shipping (
order_id INT PRIMARY KEY,
receiver_name VARCHAR(50),
receiver_phone VARCHAR(20),
address TEXT,
courier_company VARCHAR(20),
tracking_no VARCHAR(50),
shipped_at DATETIME,
delivered_at DATETIME
);
3. 大字段分离
TEXT/BLOB/JSON 类型强制分离:
CREATE TABLE article (
id INT PRIMARY KEY,
title VARCHAR(200),
summary VARCHAR(500),
author_id INT,
status TINYINT,
created_at DATETIME
); -- 列表页常用字段
CREATE TABLE article_content (
article_id INT PRIMARY KEY,
content LONGTEXT, -- 文章正文,仅详情页使用
html_content LONGTEXT -- 渲染后的 HTML
); -- 大字段单独存放
垂直拆分的好处
- 减少 IO 量:查询热点字段时不需要读取整行的数据
- 提高缓存效率:每行变短,每页可放更多行,Buffer Pool 利用率提升
- 减少锁冲突:拆分后的两张表各自的写操作互不干扰
- 便于扩展:不同业务模块的表可以放在不同实例上
- 绕过行大小限制:大字段行外存储,但小表更容易控制在 8KB 内
垂直拆分的代价
- 增加查询复杂度:原来一次查询可能变成多次或需要 JOIN
- 事务范围扩大:跨表更新需要分布式事务(XA 或业务补偿)
- 外键约束失效:不能跨表设置外键
- ORM 映射复杂:需要额外处理关联关系
垂直拆分的实施策略
延迟加载
只在需要时才查询扩展表:
1. 先查 user_base 展示用户列表
2. 点击某个用户详情时,再 JOIN user_ext 查详细信息
读写分离策略
-- 使用视图(不推荐,性能差)
CREATE VIEW user_full AS
SELECT u.*, e.*
FROM user_base u
LEFT JOIN user_ext e ON u.id = e.user_id;
-- 推荐:应用层组装
SELECT * FROM user_base WHERE id = ?; -- 先查热数据
-- 如果需要扩展信息,再查
SELECT * FROM user_ext WHERE user_id = ?;
利用垂直拆分做分库
当垂直拆分后,如果某张表仍太大,可以将不同业务的表分到不同的数据库实例上:
– user_db:存放 user_base、user_ext
– order_db:存放 order_base、order_payment、order_shipping
实战建议
| 字段类型 | 建议处理 |
|---|---|
| 常用小字段(id, name, status) | 留在主表 |
| 大文本(content, remark) | 强制分离 |
| 低频字段(备注、内部标记) | 分离到扩展表 |
| 业务完全独立(支付、物流) | 按模块分离 |
| JSON 字段 | 如果不大可以保留,大则分离 |
面试常问题
Q:垂直拆分的粒度怎么控制?
A:通常以”业务模块”为单位拆分,结合”访问频率”(热点/冷数据)。一条黄金经验:主表不超过 20 个常用字段。
Q:拆分后我需要 JOIN 两张表,性能怎么办?
A:拆分的目的是:大部分查询只用到主表(不走 JOIN),只有特定场景才 JOIN。如果所有查询都 JOIN,说明拆分方案不对。
Q:垂直拆分和水平拆分怎么选?
A:垂直拆分解决”表太宽”问题,水平拆分解决”表太大(行数太多)”问题。两者不冲突,可以先用垂直拆分让行变窄,再用水平拆分分散行。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容