文章

子表、从表、拓展表与纵表

子表、从表、拓展表与纵表

别再搞混了!一文讲清子表、从表、拓展表与纵表

你是否有过这样的经历:听到“子表”“从表”“拓展表”“纵表”这些词,明明感觉有点熟,但一到设计表结构时就卡壳,生怕用错?我曾经也是其中之一,尤其是“子表”和“从表”这对名词,一度以为是两种不同的东西。为了彻底理清,我把它们整理成了一张速查表。如果你现在也正被这些概念困扰,别急,这张表先送给你。

1 分钟速查表

对比维度子表
(又称从表)
拓展表
(垂直分表)
纵表
(行式存储)
数量关系1 : N1 : 11 : N
主要用途存明细项存不常用属性存动态属性
特征与主表一对多
子表不是独立对象
与主表一对一
与主表同一对象
表字段极简(attr_key, attr_value)
典型场景订单表
VS
订单明细表
产品基本信息表
VS
产品高级信息表
产品基本信息表
VS
产品附加信息表

看完这张表,你可能会觉得:“哦,有点意思,但里头的细节还是想再嚼一嚼。” 接下来我们就从最常见的“子表/从表”讲起,一个一个吃透它们。


核心概念拆解

1. 子表(从表):存明细,一对多

首先要澄清一个常年的误会:子表和从表其实是同一个东西。它们都是指在一对多关系中,代表“多”的那一方的表。唯一的区别在于称呼角度:站在主表看,它是子表;站在关联关系看,它常被叫作从表。

子表的核心作用是存储主表的一条记录所包含的多条明细项。比如一个订单(主表)会包含多个商品明细(子表)。这种场景下,子表不能脱离主表独立存在——订单明细若没有订单,业务上就失去了意义。这就是“子表不是独立对象”的含义,其实是一种强烈的组合/包含关系,而非简单的关联。

设计上,子表必定有一个外键指向主表的主键,通常还会和主表的主键组成联合主键,或者自身有独立主键、并建立普通索引来优化查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 主表:订单
CREATE TABLE orders (
    id            BIGINT PRIMARY KEY,
    order_number  VARCHAR(50),
    customer_name VARCHAR(100),
    total_amount  DECIMAL(10,2)
);

-- 子表(从表):订单明细
CREATE TABLE order_items (
    id         BIGINT PRIMARY KEY,
    order_id   BIGINT NOT NULL,          -- 外键指向 orders
    product_id BIGINT,
    quantity   INT,
    unit_price DECIMAL(10,2),
    FOREIGN KEY (order_id) REFERENCES orders(id)
);

2. 拓展表(垂直分表):拆字段,减负担

拓展表走得是一对一的路子,它与主表共享同一个对象实体,只是因为性能、业务隔离或者字段使用频率的考虑,把一个表的字段拆分到两张表中。这属于典型的垂直分表

常见驱动来自两股力量:一是某些“不常用”字段又大又多,二是不想让核心查询总是被这些大字段拖累。例如,产品表中我们日常查询只关心名称、价格、图片,而对于像《产品工艺说明》《售后维修政策》这样动辄几千字的富文本,用户只是偶尔翻阅详情时才用到。把它们移到拓展表后,主表体积减小、查询变快,拓展表只在需要时 JOIN。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 主表:产品基本信息(高频字段)
CREATE TABLE product_base (
    id            BIGINT PRIMARY KEY,
    name          VARCHAR(200),
    price         DECIMAL(10,2),
    stock         INT
);

-- 拓展表:产品高级信息(低频大字段)
CREATE TABLE product_ext (
    product_id    BIGINT PRIMARY KEY,   -- 既是主键又是外键,保证一对一
    description   TEXT,                 -- 富文本介绍
    warranty_policy TEXT,
    tech_spec     JSON,
    FOREIGN KEY (product_id) REFERENCES product_base(id)
);

要点:拓展表的主键通常直接就是外键,以此锁定一对一的约束。它与主表描述的是同一个业务对象,只是字段存放位置不同。这点与子表的“包含多个不同明细”有着本质区别。

3. 纵表(行式存储):要灵活,用键值

纵表这个名字很形象,它不再像普通表那样有密密麻麻的横向字段,而是只保留极少数通用列,把原本的字段名和字段值分别作为数据行来存储。典型结构只有三列:主体ID、属性键(key)、属性值(value)。

纵表主要用于动态属性扩展。当不同产品需要添加的属性千差万别,而且这些属性还会经常变动时,横表(常规表)要不断加列,显然不现实。纵表则让每一条属性都变成一行记录,随意增删,完美应对强灵活性的需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 主表:产品基本信息
CREATE TABLE product (
    id   BIGINT PRIMARY KEY,
    name VARCHAR(200)
);

-- 纵表:产品附加信息(KV结构)
CREATE TABLE product_attributes (
    id         BIGINT PRIMARY KEY,
    product_id BIGINT,
    attr_key   VARCHAR(100),   -- 属性名,如 "color", "material"
    attr_value TEXT,           -- 属性值,如 "Blue", "Stainless Steel"
    FOREIGN KEY (product_id) REFERENCES product(id)
);

这里也常常会与子表混淆,因为纵表与主表同样是一对多的关系。但区别在于:子表存的是结构化明细对象(每行各有明确的业务含义,如商品、数量、单价),而纵表存的是离散的属性条目(key-value 对)。一个是在描述明细,另一个是在扩充描述能力本身。


一对多却不叫子表?关键看“包含”

有个特例值得强调:如果两个对象是一对多,但它们互相独立,不存在强烈的内部包含关系,那多的一方也不能称为子表。例如:

  • 订单 VS 订单明细:明细是订单的内部组成部分,高度耦合,经典的子表。
  • 产品 VS 商品:一个产品下可能有多个商品(比如不同规格的 SKU),但商品本身是一个独立的业务实体,有自己完整的生命周期,可以脱离产品概念被单独理解和管理。虽然商品表里有 product_id 外键,但这种关系更适合叫“关联表”,而不是“子表/从表”。

从数据库设计角度,这种区分会影响我们如何处理级联删除、事务边界和领域模型的划分。通常,子表的生命周期应由主表控制(主表删了,子表理当一起删),而独立对象的关联表则应谨慎考虑是否级联。


总结:如何不再搞混?

下次再听到这三个概念,你可以默念对应口诀:

  • 听到“子表/从表” → 一对多,存明细,组合关系,离了主表没意义。
  • 听到“拓展表” → 一对一,拆字段,对象还是那一个,只是分了家。
  • 听到“纵表” → 一对多,KV 结构,为动态属性而生,牺牲了查询便捷性换来了灵活性。

回到最初的速查表,它此刻是不是已经变得非常顺眼了?如果你也是从“分不清”到“门儿清”,希望这篇文章能帮你节省一些查阅和试错的时间。当然,实际设计中这几种模式往往会组合出现,但只要抓准各自的底层逻辑,再复杂的表结构也能看得分明。

本文由作者按照 CC BY 4.0 进行授权