guidetutorialreferencedevopsflowchart-basics

实体关系图:数据库设计实用指南

学习如何为数据库建模创建 ER 图。涵盖实体、属性、关系、基数、陈氏符号与鸦脚符号对比,以及真实模式示例。

2 分钟阅读

没有图表就直接编写的数据库模式是一种猜测。你可能第一次就把表格设计对了,但你几乎肯定会漏掉一个关系、将外键放错位置,或者构建出一个抵制你的应用程序实际所需查询的结构。实体关系图——ER 图——是在写第一行 SQL 之前设计和传达数据库结构的标准工具。

ER 图不仅仅在初始设计时有用,在系统整个生命周期中都有价值:为需要理解现有模式的工程师提供入职指导、规划迁移、识别规范化问题,以及为非技术利益相关者记录数据模型。

什么是 ER 图?

ER 图(实体关系图)是数据库中实体及其关系的视觉表示。它显示系统存储哪些数据、每个实体有哪些属性,以及实体之间如何相互关联。

该概念由 Peter Chen 于 1976 年提出,并被数据库工具和从业者不断扩展和改进。现代实践中主导的两种符号风格:陈氏符号(概念设计)和鸦脚符号(以实现为重点,被大多数现代工具使用)。

ER 图在不同的抽象层次运作:

层次 目的 受众
概念层 高级别实体和关系 业务利益相关者、产品经理
逻辑层 添加属性、键、基数 数据库架构师
物理层 数据类型、索引、外键、SQL 就绪 开发人员、数据库管理员

关键概念

实体

实体是数据库存储信息的事物。它映射到实现数据库中的一个表。

  • 强实体:独立存在。有自己的主键。示例:CustomerProductOrder
  • 弱实体:依赖另一个实体而存在。示例:OrderLineItem 依赖于 Order

实体命名规范:

  • 使用单数名词(Order,而非 Orders
  • PascalCase 在实体名称中很常见
  • 命名行所代表的内容,而非集合

属性

属性是实体的属性。它们映射到实现表中的列。

属性类型 描述 示例
简单 单一原子值 firstNameprice
复合 由子属性组成 address = 街道 + 城市 + 邮编
派生 从其他属性计算 agebirthDate 派生
多值 可以有多个值 phoneNumbers(一个人可以有多个)
键属性 唯一标识实体实例 customerIdemail

在物理 ER 图中,在属性名称旁边指定数据类型:

Customer
├── customerId  INT PRIMARY KEY
├── email       VARCHAR(255) UNIQUE NOT NULL
├── firstName   VARCHAR(100) NOT NULL
├── lastName    VARCHAR(100) NOT NULL
├── createdAt   TIMESTAMP DEFAULT NOW()
└── status      ENUM('active', 'inactive', 'banned')

关系

关系定义两个实体之间的关联方式。它映射到实现模式中的外键约束(或多对多关系的连接表)。

关系具有:

  • 名称:描述关联(placescontainsbelongs to
  • 基数:每个实体可以参与多少个实例
  • 参与度:参与是强制的(全部)还是可选的(部分)

基数

基数是 ER 建模中最重要的概念。它定义了实体实例之间的数量关系。

基数 含义 示例
一对一 一个 A 实例恰好关联一个 B 实例 User 有一个 Profile
一对多 一个 A 实例关联多个 B 实例 Customer 下了多个 Orders
多对一 多个 A 实例关联一个 B 实例 多个 Orders 属于一个 Customer
多对多 多个 A 实例关联多个 B 实例 Students 注册多个 Courses

多对多关系不能直接在关系数据库中实现。它们需要一个连接表(也称为关联表或联结实体):

Student ──< Enrollment >── Course

Enrollment 表保存指向 StudentCourse 的外键,以及注册本身的任何属性(如 enrollmentDategrade)。

符号风格

陈氏符号

陈氏符号是原始的学术符号。它使用几何形状:

  • 矩形:实体
  • 椭圆:属性
  • 菱形:关系
  • 直线:连接
  • 双椭圆:多值属性
  • 虚线椭圆:派生属性
  • 双矩形:弱实体
  • 双菱形:标识关系(用于弱实体)

陈氏符号 ASCII 示例:

 (customerId)   (email)    (firstName)
      |             |           |
      └─────────────┴───────────┘
                    |
              ┌─────────┐
              │Customer │
              └────┬────┘
                   │
              <  places  >
                   │
              ┌─────────┐
              │  Order  │
              └─────────┘

陈氏符号对概念建模很有用,因为它明确显示关系和属性。对于大型模式来说会显得混乱。

鸦脚符号

鸦脚符号是现代数据库工具(MySQL Workbench、dbdiagram.io、ERDPlus)中的主流风格。它使用关系线两端的符号直接编码基数。

鸦脚符号:

线端符号           含义
─────────────    ──────────────────────────────
      ──|         恰好一个(强制)
      ──o         零或一个(可选)
      ──<         多个(一或多)
      ──o<        零或多个(可选多)
      ──|<        一或多个(强制多)
      ──||        恰好一个(两端)

鸦脚符号示例——电商关系:

Customer ||──o< Order ||──|< OrderLineItem >|──|| Product

解读:一个 Customer 有零或多个 Orders;每个 Order 有一或多个 OrderLineItems;每个 OrderLineItem 恰好引用一个 Product。

带鸦脚符号的完整实体:

┌─────────────┐       ┌─────────────┐       ┌──────────────────┐
│  Customer   │       │    Order    │       │  OrderLineItem   │
│─────────────│       │─────────────│       │──────────────────│
│ PK id       │||──o<─│ PK id       │||──|<─│ PK id            │
│    email    │       │ FK customerId│      │ FK orderId       │
│    name     │       │    status   │       │ FK productId     │
│    createdAt│       │    total    │       │    quantity      │
└─────────────┘       │    createdAt│       │    unitPrice     │
                      └─────────────┘       └──────────────────┘

鸦脚符号适合逻辑和物理 ER 图,因为它比陈氏符号更紧凑,并直接反映 SQL 模式结构。

数据库设计过程中的 ER 图

ER 图在多个阶段都融入数据库设计:

1. 需求收集 与利益相关者合作,确定系统需要跟踪的事物。需求中的每个名词都是潜在实体。关于名词的每个事实都是潜在属性。

2. 概念设计 绘制实体和关系,不用担心实现细节。专注于数据的含义,而非存储方式。

3. 逻辑设计 添加属性、定义主键、将多对多关系解析为连接表、指定基数。

4. 物理设计 添加数据类型、索引、约束和 NULL/NOT NULL 规范。此阶段的 ER 图应该直接映射到 SQL DDL。

5. 审查和精化 针对实际查询验证模式:你的应用程序能否高效检索所需的数据?模式是否支持所有必需的操作?

分步创建指南

第一步:识别实体

通读需求或用户故事,高亮代表系统跟踪的不同概念的名词。从显而易见的开始。

对于电商系统:

  • Customer、Product、Order、Category、Review、Address、Payment

经验法则:

  • 如果有多个实例,可能是实体(而非属性)
  • 如果有自己的属性,可能是实体
  • 如果其他实体独立引用它,那就是实体

第二步:为每个实体定义属性

对于每个实体,列出其属性。识别:

  • 主键(唯一标识符,系统生成或自然键)
  • 必需属性(SQL 中的 NOT NULL)
  • 可选属性
  • 可能是复合的属性(address)或多值的属性(tags)

避免存储派生数据,除非性能要求如此。age 可以从 birthDate 派生;两者都存储会产生一致性问题。

第三步:映射关系

对于相互交互的每对实体,定义关系:

  1. 命名关系(从一个实体的角度使用动词短语)
  2. 确定基数(一对一、一对多、多对多)
  3. 确定参与度(每一侧是强制的还是可选的?)

用句子写出来并验证两个方向都说得通:

  • "一个 Customer 下了零或多个 Orders"
  • "一个 Order 恰好由一个 Customer 下单"

第四步:解析多对多关系

每个多对多关系都变成一个连接表。识别关系本身的属性(如有)——这些成为连接表中的列。

Student ──< Enrollment >── Course

Enrollment 属性:
- enrollmentDate
- grade
- status (active, withdrawn, completed)

第五步:分配数据类型和约束

对于每个属性,指定:

  • 数据类型(INTVARCHAR(n)DECIMAL(10,2)TIMESTAMPBOOLEAN
  • NULL / NOT NULL
  • 默认值
  • 唯一约束
  • 检查约束(例如 price > 0

第六步:针对查询验证

写出应用程序将运行的关键查询并通过模式追踪它们。如果一个查询需要多个本可以通过更好的设计避免的连接,那就重新审视模式。

检查:

  • 你能检索客户的完整订单历史记录吗?
  • 你能找到某个类别中的所有产品吗?
  • 你能从行项目计算订单总额吗?
  • 你能按日期范围生成收入报告吗?

规范化注意事项

规范化是对模式进行结构化以减少数据冗余并提高完整性的过程。最常应用的三种范式:

第一范式(1NF): 每列包含原子值。没有重复组。每行可唯一标识。

第二范式(2NF): 满足 1NF。每个非键属性完全依赖于整个主键(对复合键相关)。

第三范式(3NF): 满足 2NF。没有传递依赖——非键属性只依赖于主键,而非其他非键属性。

3NF 违规示例:在 Order 表中存储 customerCity。城市依赖于客户,而非订单。如果客户搬家,你必须更新每个订单记录。

何时去规范化: 规范化优化写入一致性。读取密集的分析工作负载有时受益于去规范化的结构(物化视图、报告表),这些结构可避免昂贵的连接。这应该是有意为之的决定,而非意外。

常见错误

将自然键用作主键。 电子邮件地址会变更。不应存储社会安全号码。名字不是唯一的。使用代理键(自增整数或 UUID)作为主键,并在自然标识符上添加唯一约束。

在单列中存储列表。 包含逗号分隔值的 tags 列违反 1NF。使用连接表(ProductTag)代替。

缺少连接表属性。 解析多对多关系时,团队通常只创建一个只有两个外键的裸连接表。考虑关系本身的属性——注册日期、订单数量、权限级别。

混淆实体和属性。 在简单系统中,Address 可能是 Customer 上的属性。在多个客户共享地址,或地址有自己生命周期的系统中,Address 应该是单独的实体。

不考虑软删除。 许多系统出于审计或监管原因需要在"删除"后保留记录。提前决定是使用 deletedAt 时间戳还是 isActive 标志,并一致应用。

对小数据集过度规范化。 将每个重复字符串提取到查找表中,对于永远不会超过数千行的数据集来说,增加了复杂性而没有实际好处。运用判断,而非机械地遵循规则。

忽视索引设计。 ER 图显示结构,而非性能。确定模式后,识别将在 WHERE 子句和 JOIN 条件中使用的列,并为其添加索引。

真实案例:电商模式

以下模式涵盖了典型电商系统的核心实体。

┌──────────────┐     ┌──────────────┐     ┌───────────────────┐
│   Customer   │     │    Order     │     │   OrderLineItem   │
│──────────────│     │──────────────│     │───────────────────│
│ PK id        │     │ PK id        │     │ PK id             │
│    email     │||─o<│ FK customerId│||─|<│ FK orderId        │
│    firstName │     │ FK addressId │     │ FK productId      │
│    lastName  │     │    status    │     │    quantity       │
│    createdAt │     │    subtotal  │     │    unitPrice      │
│    status    │     │    tax       │     └────────┬──────────┘
└──────────────┘     │    shipping  │              │
                     │    total     │         恰好一个
                     │    createdAt │              │
                     └──────────────┘     ┌────────┴──────────┐
                                          │      Product      │
┌──────────────┐                          │───────────────────│
│   Address    │                          │ PK id             │
│──────────────│                          │ FK categoryId     │
│ PK id        │                          │    name           │
│ FK customerId│                          │    description    │
│    line1     │                          │    sku            │
│    line2     │                          │    price          │
│    city      │                          │    stockQty       │
│    state     │                          │    isActive       │
│    zip       │                          └────────┬──────────┘
│    country   │                                   │
│    isDefault │                              多个│属于
└──────────────┘                                   │一个
                                          ┌────────┴──────────┐
┌──────────────┐                          │     Category      │
│   Payment    │                          │───────────────────│
│──────────────│                          │ PK id             │
│ PK id        │                          │ FK parentId       │
│ FK orderId   │                          │    name           │
│    method    │                          │    slug           │
│    amount    │                          └───────────────────┘
│    status    │
│    processedAt│
└──────────────┘

此模式中的关键设计决策:

  • Address 是单独的实体(客户可以有多个地址;订单引用购买时使用的地址)
  • OrderLineItem.unitPriceProduct.price 分开存储(价格会变化;订单历史必须反映实际收费)
  • Category 有自引用 parentId 用于层次化类别
  • PaymentOrder 分开,因为一个订单可能涉及多次支付尝试

使用 Flowova 创建 ER 图

手动或在纯文本编辑器中设计模式很慢。Flowova 的 ER 图生成器让你用纯文本描述数据模型,并生成可以精修的结构化图表。从实体列表开始,描述它们之间的关系,即可获得可与团队共享审查的可视化模式。

结论

ER 图是良好数据库设计的基础。它们迫使你明确说明系统存储哪些数据、什么使每条记录唯一,以及实体之间如何相互关联——在这些决策在 SQL 中变得昂贵之前。从包含实体和关系的概念层开始,通过添加属性和解析多对多连接进入逻辑层,然后为物理设计添加数据类型和约束。针对真实查询验证,在重要的地方应用规范化,并记录模式,让下一位工程师无需阅读迁移文件就能理解它。

相关资源

相关文章

准备好试用 AI 流程图生成器了吗?

加入数万名使用 Flowova 可视化想法的专业人士。几秒钟内开始用 AI 创建流程图。

免费开始