表字段过多时的垂直拆分策略

表字段过多时的垂直拆分策略

什么是垂直拆分

垂直拆分(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
);  -- 大字段单独存放

垂直拆分的好处

  1. 减少 IO 量:查询热点字段时不需要读取整行的数据
  2. 提高缓存效率:每行变短,每页可放更多行,Buffer Pool 利用率提升
  3. 减少锁冲突:拆分后的两张表各自的写操作互不干扰
  4. 便于扩展:不同业务模块的表可以放在不同实例上
  5. 绕过行大小限制:大字段行外存储,但小表更容易控制在 8KB 内

垂直拆分的代价

  1. 增加查询复杂度:原来一次查询可能变成多次或需要 JOIN
  2. 事务范围扩大:跨表更新需要分布式事务(XA 或业务补偿)
  3. 外键约束失效:不能跨表设置外键
  4. 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
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容