学 ER 图最快的方式就是看实际例子。实体、属性、基数这些抽象概念,放到你熟悉的场景里——商店、学校、医院——立刻就能理解了。
本文展示 5 个完整的 ER 图实例,每个来自不同领域。每个例子都会覆盖实体、关键属性、实体之间的关系以及值得注意的设计决策。如果你对 ER 图的基础概念还不熟悉,建议先看 ER 图是什么。
1. 电商数据库
在线商店需要追踪客户、商品、订单和支付。这可能是课程和面试中最常见的 ER 图类型。
实体:
- Customer(客户) — id (PK), name, email, phone, address, created_at
- Product(商品) — id (PK), name, description, price, stock_qty, category_id (FK)
- Category(分类) — id (PK), name, parent_category_id (FK, 自引用)
- Order(订单) — id (PK), placed_at, status, shipping_address, customer_id (FK)
- OrderItem(订单项) — id (PK), order_id (FK), product_id (FK), quantity, unit_price
- Payment(支付) — id (PK), order_id (FK), method, amount, paid_at, status
关系:
- Customer → Order:一对多。一个客户可以下多个订单,每个订单属于一个客户。
- Order → OrderItem:一对多。一个订单包含多个订单项。
- Product → OrderItem:一对多。一个商品可以出现在多个订单项中。
- Order → Payment:一对一(如果允许拆分支付则是一对多)。
- Category → Product:一对多。一个分类下有多个商品。
- Category → Category:自引用一对多。分类可以嵌套(电子产品 → 笔记本电脑 → 游戏本)。
设计要点:
unit_price 存在 OrderItem 上,而不是查询时从 Product 读取。因为价格会变——查看历史订单时,你需要的是下单时的价格,不是当前价格。这是初学者最常犯的错误之一。
Product 和 Order 之间是多对多关系,通过 OrderItem 这个中间表解决。OrderItem 自带数据(quantity、unit_price),不只是个链接表。
2. 学校/大学数据库
学校系统追踪学生、课程、教师和选课记录。这个例子展示了中间表携带有意义数据的场景。
实体:
- Student(学生) — id (PK), name, email, enrollment_date, major
- Course(课程) — id (PK), code, title, credits, department_id (FK)
- Instructor(教师) — id (PK), name, email, title, department_id (FK)
- Department(院系) — id (PK), name, head_instructor_id (FK)
- Enrollment(选课记录) — id (PK), student_id (FK), course_id (FK), semester, grade, enrolled_at
关系:
- Student → Enrollment → Course:多对多(通过 Enrollment)。学生选多门课,课程有多个学生。Enrollment 记录了每个学生-课程组合的学期和成绩。
- Instructor → Course:一对多。一个老师教多门课。
- Department → Course:一对多。课程属于院系。
- Department → Instructor:一对多。教师属于院系。
- Department → Instructor(系主任):一对一。每个院系有一个系主任。
设计要点:
Enrollment 是 Student 和 Course 之间的中间表,但它不只是两个外键。它还有 semester、grade、enrolled_at——这些数据属于关系本身,不属于任何一个实体。当你的中间表有自己的属性时,就应该给它一个独立的主键,把它当作正式实体来对待。
Department 有个 head_instructor_id 引用 Instructor,而 Instructor 又有 department_id 引用 Department。这形成了循环引用——在 ER 图中没问题,但初始化数据库数据时要注意插入顺序。
3. 医院/医疗数据库
医疗数据库比大多数教学示例展示的要复杂得多。这里做了简化,但保留了核心结构。
实体:
- Patient(患者) — id (PK), name, date_of_birth, gender, phone, emergency_contact, insurance_id (FK)
- Doctor(医生) — id (PK), name, specialization, license_number, department_id (FK)
- Department(科室) — id (PK), name, floor, building
- Appointment(预约) — id (PK), patient_id (FK), doctor_id (FK), scheduled_at, status, notes
- MedicalRecord(病历) — id (PK), patient_id (FK), doctor_id (FK), diagnosis, treatment, created_at
- Insurance(保险) — id (PK), provider_name, policy_number, coverage_type, expiry_date
关系:
- Patient → Appointment:一对多。一个患者可以有多次预约。
- Doctor → Appointment:一对多。一个医生处理多个预约。
- Patient → MedicalRecord:一对多。每次就诊产生一条病历。
- Doctor → MedicalRecord:一对多。主治医生关联到每条病历。
- Doctor → Department:多对一。医生属于科室。
- Patient → Insurance:多对一。多个患者可以共享同一个保险计划。
设计要点:
Appointment 和 MedicalRecord 都关联了 Patient 和 Doctor,但它们代表不同的事情。预约是一个计划中的事件(未来或过去);病历是就诊的临床结果。分开存储让你可以追踪爽约(有预约没病历)和未预约就诊(有病历没预约)。
Insurance 作为独立实体而非 Patient 上的列。这避免了多个家庭成员共享同一保险计划时的数据重复。
4. 图书馆管理数据库
图书馆系统是经典的 ER 图练习题,因为它的结构干净、关系清晰。
实体:
- Book(图书) — id (PK), isbn, title, published_year, genre, shelf_location
- Author(作者) — id (PK), name, bio, nationality
- BookAuthor(图书-作者) — book_id (FK), author_id (FK) [复合主键]
- Member(读者) — id (PK), name, email, phone, membership_type, joined_at
- Loan(借阅记录) — id (PK), book_id (FK), member_id (FK), borrowed_at, due_at, returned_at
- Fine(罚款) — id (PK), loan_id (FK), amount, paid, issued_at
关系:
- Book → Author:多对多(通过 BookAuthor)。一本书可以有多个作者,一个作者可以写多本书。
- Member → Loan:一对多。一个读者可以多次借书。
- Book → Loan:一对多。一本书可以被多次借出(按时间顺序)。
- Loan → Fine:一对一(或一对零)。逾期的借阅可能关联一笔罚款。
设计要点:
BookAuthor 是纯粹的中间表,没有额外属性——book_id + author_id 的组合键就够了。对比电商的 OrderItem(有 quantity 和 price)和学校的 Enrollment(有 semester 和 grade),区别在于你的中间表是否有自己的属性。
Loan 上的 returned_at 可以为空。如果为空,说明书还没还;如果有值,说明书已归还。这一个字段就让你能同时查询"当前在借"和"历史记录",不需要两张表。
Fine 关联到 Loan 而不是直接关联到 Member。这保留了完整的事件链:某次借阅逾期了,所以产生了罚款。通过 Loan 总能找到对应的读者。
5. 社交平台数据库
社交平台的数据模型一旦加上帖子、评论、点赞、关注、通知,复杂度就上来了。这里是简化版。
实体:
- User(用户) — id (PK), username, email, display_name, bio, avatar_url, created_at
- Post(帖子) — id (PK), user_id (FK), content, media_url, created_at, updated_at
- Comment(评论) — id (PK), post_id (FK), user_id (FK), content, created_at
- Like(点赞) — id (PK), user_id (FK), post_id (FK), created_at [user_id + post_id 唯一约束]
- Follow(关注) — id (PK), follower_id (FK → User), following_id (FK → User), created_at
- Notification(通知) — id (PK), user_id (FK), type, reference_id, is_read, created_at
关系:
- User → Post:一对多。用户发布多条帖子。
- Post → Comment:一对多。帖子收到多条评论。
- User → Comment:一对多。用户写多条评论。
- User → Like → Post:多对多(通过 Like)。用户可以点赞多个帖子,帖子可以被多人点赞。
- User → Follow → User:自引用多对多(通过 Follow)。用户关注其他用户。follower_id 和 following_id 都指向 User。
- User → Notification:一对多。用户收到多条通知。
设计要点:
Follow 是自引用多对多关系。Follow 表有两个外键都指向 User——follower_id(谁关注了)和 following_id(被谁关注)。这和对称关系不同:Alice 关注了 Bob,不代表 Bob 也关注了 Alice。
Like 本质上是 User 和 Post 的中间表,但自带 created_at 来记录点赞时间。(user_id, post_id) 上的唯一约束防止同一用户重复点赞。
Notification 用了一个通用的 reference_id,根据通知类型(type)可以指向帖子、评论或关注。这是一种务实的反范式设计——另一种做法是每种通知类型一张表,更规范但查询更难写。
5 个例子中的共同规律
看完 5 个不同领域的例子,几个规律很明显:
多对多关系一定要用中间表。 电商有 OrderItem,学校有 Enrollment,图书馆有 BookAuthor,社交平台有 Like 和 Follow。在关系型数据库中没有捷径。
中间表经常带有自己的数据。 OrderItem 有 quantity 和 price,Enrollment 有 semester 和 grade。当中间表有自己的属性时,给它独立的主键,把它当正式实体对待。
自引用关系比你想的更常见。 分类层级(电商)、系主任(学校)、用户关注(社交平台)——都涉及一张表引用自己。
可空外键代表可选关系。 Loan 的 returned_at 为空表示书还在借;Fine 关联到 Loan 表示这次借阅逾期了。可空性是设计决策,不是事后补充。
自己动手试试
学 ER 图最好的办法是自己画。选一个你熟悉的领域——餐厅、健身房会员、音乐流媒体——然后走一遍流程:
- 找出实体(名词)
- 列出每个实体的属性
- 找到实体之间的关系
- 确定基数(1:1、1:N、M:N)
- 需要的地方加中间表
- 画出图
可以用我们的 ER 图模板 快速开始——已经预置了常见的实体形状和关系连接线。画图过程的详细教程,可以看 ER 图怎么画。



