免费 · 无需注册 · 支持 .mmd 文件

Mermaid ER 图编辑器

实体关系图展示表、表里的列,以及它们如何关联。当主题是数据库结构、而关心的问题是主外键和基数时用它——一对多、可选、或者通过一张连接表关联。如果对象带有行为和继承,请用类图。

带连接表的规范化订单结构

订单明细这张连接表才是值得琢磨的部分:它是诚实表达多对多关系的方式——通过一张有自己列的独立表,而不是假装两张表可以直接相连。

erDiagram
    客户 ||--o{ 订单 : 提交
    订单 ||--|{ 订单明细 : 包含
    商品 ||--o{ 订单明细 : "出现于"
    客户 ||--o{ 收货地址 : "寄送至"

    客户 {
        int id PK
        string 邮箱 UK "写入时转小写"
        string 姓名
        datetime 创建时间
    }
    订单 {
        int id PK
        int 客户id FK
        string 状态
        decimal 总金额
    }
    订单明细 {
        int 订单id PK, FK
        int 商品id PK, FK
        int 数量
        decimal 单价 "下单时点的价格"
    }
    商品 {
        int id PK
        string 商品编码 UK
        string 名称
    }
    收货地址 {
        int id PK
        int 客户id FK
        string 邮编
    }
在编辑器中打开
广告

实例讲解

1. 两个实体和一条关系

每条 ER 关系在冒号后面都必须有标签——这一点和流程图的边不同,标签不是可选的。把它当成一句话来读:客户 提交 订单。

erDiagram
    客户 ||--o{ 订单 : 提交
在编辑器中打开

2. 补上列

每一行属性都是 `类型 名称`,两部分都必需。`PK`、`FK`、`UK` 标记键;末尾加引号的字符串是该列的注释。

erDiagram
    客户 ||--o{ 订单 : 提交
    客户 {
        int id PK
        string 邮箱 UK
        string 姓名
    }
    订单 {
        int id PK
        int 客户id FK
        datetime 下单时间 "UTC"
    }
在编辑器中打开

3. 表达真实约束的基数

紧挨着每个实体的那两个字符就是它的基数。这个例子说的是:一笔订单至少要有一条明细,但一个客户可以一笔订单都没有——这是一条数据库应当强制、图也应当展示的真实约束。

erDiagram
    客户 ||--o{ 订单 : 提交
    订单 ||--|{ 订单明细 : 包含
    订单明细 }o--|| 商品 : 引用
在编辑器中打开

4. 通过连接表表达多对多

Mermaid 可以直接画一条多对多关系,但真实结构里很少有——底下总有一张连接表。把它画出来更诚实,也给那些本就属于关系本身的列找到了归处。

erDiagram
    学生 ||--o{ 选课 : "报名"
    课程 ||--o{ 选课 : "被选修"
    选课 {
        int 学生id PK, FK
        int 课程id PK, FK
        datetime 选课时间
        string 成绩 "评分前为空"
    }
    学生 {
        int id PK
        string 姓名
    }
    课程 {
        int id PK
        string 课程号 UK
    }
在编辑器中打开

5. 自引用关系

一张表可以关联到它自己——汇报关系、分类树、评论楼中楼。这里的关系标签比平时更重要,因为两端是同一个实体。

erDiagram
    员工 ||--o{ 员工 : 管理
    员工 {
        int id PK
        int 上级id FK "总经理为空"
        string 姓名
        string 职位
    }
在编辑器中打开

ER 图语法速查

基数写作关系两端各两个字符,读法是从中间往外读。左边那一对描述左边的实体,右边那一对描述右边的实体。

语法含义
erDiagram开启该图。区分大小写。
A ||--o{ B : 文字关系。标签是必需的。
||恰好一个。
o|零个或一个。
}|一个或多个。
}o零个或多个。
--标识性关系——实线。
..非标识性关系——虚线。
A { ... }实体 A 的属性块。
int id PK属性:先类型,再名称,最后是可选的键标记。
PK / FK / UK主键、外键、唯一键。
int id PK, FK多个键标记,用逗号分隔。
string 邮箱 "注释"末尾加引号的字符串是该列的注释。
A ||--o{ B : "两个词"只要标签里有空格,引号就是必需的。不加引号时标签会在第一个空格处被截断,后面的每个词都会变成一个多余的实体。
广告

会让 ER 图报错的几种情况

ER 图是本站六种图里语法最严格的——在别处能正常渲染的写法,在这里会被直接拒绝。以下均以 Mermaid 11.12.2 复现。

你会看到

Parse error,结尾是:Expecting 'COLON', 'STYLE_SEPARATOR', got 'NEWLINE'

原因

关系没有标签。和流程图的边不同,冒号和标签在这里是必需的——没有它们,这一行就提前结束了。

解决办法

补上冒号和一个动词,短到能读成一句话为宜。

错误写法
erDiagram
    客户 ||--o{ 订单
正确写法
erDiagram
    客户 ||--o{ 订单 : 提交

你会看到

Parse error,结尾是:got 'UNICODE_TEXT'

原因

基数记号不合法。合法的只有 `||`、`o|`、`}|`、`}o` 以及它们的镜像,其他一律失败。`oo` 是最常见的笔误,因为大家以为这套记号是对称的。

解决办法

用这四对之一。`||--o{`——恰好一个对零个或多个——能覆盖大多数外键场景。

错误写法
erDiagram
    客户 ||--oo{ 订单 : 提交
正确写法
erDiagram
    客户 ||--o{ 订单 : 提交

你会看到

Parse error,结尾是:Expecting 'ATTRIBUTE_WORD', got 'BLOCK_STOP'

原因

属性只有名称没有类型。ER 属性是 `类型 名称`,类型不是可选的——这一点常让习惯了无模式数据库的人意外。

解决办法

给每个属性一个类型。真实结构里没有的话就自己定一个:`string`、`int`、`json`。

错误写法
erDiagram
    客户 {
        邮箱
    }
正确写法
erDiagram
    客户 {
        string 邮箱
    }

你会看到

No diagram type detected matching given configuration

原因

大小写写错。`erdiagram` 和 `ERDiagram` 都会失败,只有 `erDiagram` 可用。

解决办法

小写 e,大写 D。

错误写法
erdiagram
    A ||--o{ B : 拥有
正确写法
erDiagram
    A ||--o{ B : 拥有

你会看到

Parse error,结尾是:Expecting 'NON_IDENTIFYING', 'IDENTIFYING', got 'UNICODE_TEXT'

原因

两个基数记号之间的连线写错了。它必须恰好是两个字符——标识性关系用 `--`,非标识性关系用 `..`。单个短横线不是 `--` 的简写,而是语法错误。

解决办法

在两组基数记号之间使用 `--` 或 `..`。

错误写法
erDiagram
    客户 ||-o{ 订单 : 提交
正确写法
erDiagram
    客户 ||--o{ 订单 : 提交

你会看到

基数能渲染,表达的却是相反的约束

原因

紧挨着某个实体的两个字符属于那个实体,而这很容易读反。`客户 ||--o{ 订单` 说的是一个客户有零个或多个订单。翻成 `客户 }o--|| 订单`,你说的就变成了每个客户恰好属于一笔订单——纯属胡说,却能毫无怨言地渲染出来。

解决办法

从中间往外读:贴着“客户”的记号描述的是有多少个客户,而不是有多少笔订单。

错误写法
erDiagram
    客户 }o--|| 订单 : 提交
正确写法
erDiagram
    客户 ||--o{ 订单 : 提交

渲染须知

以下均为针对本站所用 Mermaid 11.12.2 的实测结果。

中文表名和列名会让属性表更紧凑

ER 图里每个实体都是一张小表格,宽度由最长的那一行 `类型 名称` 决定。中文列名通常比英文短得多——`string 邮箱` 明显短于 `string email_address`——所以中文 ER 图的实体框更窄,同一屏能塞下更多实体。要注意的是类型名一般仍保持英文(int、string、decimal),这本身是好事:它让类型和名称在视觉上自然分开。

布局由关系驱动,而不是实体数量

一串首尾相连的实体会排成高高的一列——四十个实体的 viewBox 大约是 116×7500;而同样四十个实体如果都连向一张中心表,得到的图会宽得多、矮得多。这是本站唯一一种“数据的形状比数据的数量更能改变图的形状”的图类型。所以一张塞不下的结构图,往往靠改变要画哪些关系来解决,而不是画多少个。

它的语法是六种图里最严格的

关系标签必需、属性类型必需、基数记号是一个封闭集合。和甘特图那种几乎什么都能渲染、错误却悄无声息的风格相比,ER 图失败得又早又响。这是优点:一张 ER 图只要画出来了,它就极有可能正是你想表达的那张。

标签是 HTML,所以 PNG 导出会重新渲染

实体的属性表格画在 SVG 的 `<foreignObject>` 里,浏览器无法直接把它光栅化到 canvas 上。本站的 PNG 导出过去会悄悄退回成 SVG 文件;现在它会先用纯 SVG 文本标签重新渲染,产出尺寸正确的 PNG,只是字体排版略有差异。

实体名区分大小写,习惯上全大写

`CUSTOMER` 和 `customer` 是两个不同的实体,在同一张图里同时用到就会悄悄多出一个方框。用中文实体名时不存在大小写问题,但如果你混用了中英文命名,同样要留意同一个实体不要出现两种写法。

什么时候该换一种图

如果你需要继承、接口或方法,那这是错的图——ER 完全没有这些概念。请用类图,并接受它建模的是你的代码而不是你的表。

如果结构很大,把整个库画成一张 ER 图就成了没人会看的挂图。只画你正要解释的那五张表,其余留白;图是一个论点,不是一份清单。

而如果真正的问题是数据如何在系统之间流动、而不是它如何存储,那么按系统分子图的流程图能回答它,ER 图不能。

其他图类型

作者 Dominik Malsch · 最后更新:

打开编辑器 →