如何用数据库设计工具绘制 ER 图并生成建表语句

资讯5小时前发布 jdkjadmin
0 00
如何用数据库设计工具绘制 ER 图并生成建表语句

如何用数据库设计工具绘制 ER 图并生成建表语句

ER 图(实体关系图)把业务中的对象、属性以及对象之间的关系表达出来。数据库设计工具可以把这张图转成建表语句,减少手写重复工作,也让表结构更容易审阅和修改。它不会替你判断业务规则是否合理:字段类型、唯一性、删除策略和索引仍需要设计者确认。

本文以 MySQL Workbench 为例,演示从业务需求建模、绘制 EER 图,到生成并检查 MySQL 建表语句的流程。示例针对 MySQL 8.0,包含用户、商品、订单和订单明细四张表。不同 Workbench 版本的菜单名称或窗口布局可能略有差异;生成的 SQL 也应以当前版本实际显示的内容为准。

一、什么情况下适合从 ER 图生成 SQL

ER 图适合用于梳理结构化数据,以及表达表之间相对稳定的关联关系。常见用途包括:

  • 新项目建库:在开发前统一实体、字段和关系,避免各自按代码习惯创建表。
  • 多人评审:让产品、开发和数据人员在同一张图上讨论主键、可空性、唯一性和关联规则。
  • 原型验证:先建立一套可执行的基础结构,再根据真实业务数据和查询负载迭代。
  • 维护存量数据库:将已有结构逆向导入模型,辅助理解依赖关系或规划改造。
  • 生成迁移脚本:把模型变更转换为 SQL 草稿,经审查后交给迁移流程执行。

以下情况需要谨慎:业务规则频繁变化、需要大量数据库专有功能、已有生产库不能随意改动,或目标数据库与建模工具支持的功能不一致。图适合解释结构,不等于完整的业务规格说明,也不能代替代码评审、迁移测试和备份。

二、示例需求与关系设计

假设我们要保存电商订单:

  • 一个用户可以有多个订单;每个订单属于一个用户。
  • 一个订单有多条订单明细;每条明细属于一个订单。
  • 每条订单明细关联一个商品,一个商品可以出现在多条明细中。
  • 订单明细保存下单时的商品单价,不能只依赖商品当前价格。

关系可以概括为:用户 1 对多订单,订单 1 对多明细,商品 1 对多明细。订单与商品之间实际是多对多关系,通过 order_items 这张关联表拆成两个一对多关系。

这个例子还体现了一个重要边界:外键只能约束数据库能够直接验证的结构关系,不能自动表达所有业务流程。例如“订单支付成功后才允许发货”通常由应用逻辑、数据库约束或专门的状态流转机制共同实现,不能仅靠 ER 图的一条连线解决。

三、先明确字段和约束

绘图前先列出实体、字段和约束,比打开工具后边画边猜更可靠。下面的设计采用以下约定:

  • 主键使用自增的有符号 BIGINT,适合多数常规业务规模;如果系统对 ID 类型、分布式生成或容量有特定要求,应另行评估。
  • 金额使用 DECIMAL(12,2),避免用浮点数保存需要精确到分的金额。
  • 名称和邮箱使用 VARCHAR;是否可空、是否唯一按业务需求设置。
  • 时间采用 DATETIME,示例默认由数据库写入当前时间。项目若采用 UTC、时区转换或统一由应用写入,应明确一致策略。
  • 订单明细的商品数量必须大于零,单价不能为负。示例使用 MySQL 8.0.16 及以上版本支持并执行的 CHECK 约束。

字段定义不能只看名字。比如用户邮箱是否允许为空、商品删除后历史订单是否保留、订单明细中的单价是否快照,都属于业务选择。下面的示例将邮箱设为非空且唯一,将商品和用户的外键删除策略设为限制删除,并将订单删除时的明细设为级联删除。

四、在 MySQL Workbench 中绘制 EER 图

1. 创建模型和关系图

打开 MySQL Workbench,选择创建新模型,在模型中添加 EER Diagram。可以使用工具栏中的表格工具逐个创建表,也可以先在模型编辑器中创建表,再从画布工具栏选择表放置到图上。新版本界面可能略有不同,关键是编辑模型中的表,而不是直接在查询窗口里执行 SQL。

建议先创建四张表,再补关系。表名使用小写蛇形命名法,例如 order_items。避免使用容易与数据库关键字冲突的名称;确实需要时,确认工具生成的引用符号符合目标数据库语法。

2. 定义列、主键和可空性

双击表格打开编辑面板,在 Columns 区域逐列添加字段。为每列填写名称和数据类型,并根据需求设置 PK(主键)、NN(非空)、UQ(唯一)和自增属性。注意:不同版本的属性名称或位置可能有变化,保存后应检查表格定义,而不是只看画布上的显示。

例如,users.id 是主键并设为自增;users.email 设置非空和唯一;orders.user_id 是非空字段,但它成为外键还需要建立关系或在外键设置中明确关联目标列。

主键、唯一约束和索引不是同一件事。主键用于标识一行,且不能为 NULL;唯一约束保证指定列或列组合不重复。普通索引主要用于查询性能,不代表数据唯一。是否需要索引应结合查询条件、排序、连接和写入成本决定。

3. 建立关系和设置删除规则

使用工具栏中的关系工具连接表。对于本例,分别建立 users 到 orders、orders 到 order_items、products 到 order_items 的一对多关系。通常先选择父表,再选择子表;最终仍应检查外键列是否落在子表,以及方向和基数是否符合预期。

如果 Workbench 在建立关系时自动创建外键列,检查列名、类型和可空性,避免又手动创建一列造成重复。外键两端的数据类型需要兼容;对于整数列,还要留意有符号与无符号属性是否一致。外键所引用的列通常必须是主键或有唯一约束的键。

在外键属性中设置删除和更新规则。示例采用:

  • 删除用户时,若仍有订单则拒绝删除,防止历史订单失去归属。
  • 删除订单时,级联删除其订单明细。
  • 商品有关联订单明细时拒绝删除,以保留历史关系;更常见的产品做法也可能是设置停用状态,而不是物理删除。

ON DELETE CASCADE 会由数据库自动删除子表相关记录,影响范围可能大于操作者预期。不要因为图上关系是“一对多”就默认级联删除。对订单、账务、审计等数据,通常需要先考虑保留要求、软删除和归档方案。

4. 补充唯一约束、检查约束和索引

在表编辑器的 Indexes 区域配置索引及唯一索引;复合唯一约束要按列组合建立,而不是给每列分别加唯一。例如,若一个系统要求同一用户的外部订单号不能重复,应建在 (user_id, external_order_no) 上,而不是单独要求全表的外部订单号唯一。

Workbench 对各种数据库对象的建模和输出能力会随版本及目标数据库而异。若界面没有提供某种约束的编辑方式,不要假设它会自动生成;可以检查 Forward Engineer 预览的 SQL,必要时将该对象通过经过审核的迁移脚本补上。

5. 排版和命名

把父表放在上方或左侧、子表放在下方或右侧,尽量避免关系线交叉。图上的位置不影响数据库执行,但清晰的布局有助于评审。命名应统一表达实体含义,字段名尽量体现单位或语义,例如 unit_price 比 price 更明确。团队还应约定字符集、排序规则、时间处理和主键策略。

五、生成建表语句

模型完成后,使用 MySQL Workbench 的 Database > Forward Engineer 流程生成目标数据库结构。向导通常会让你选择连接、对象和导出选项,并显示即将执行或输出的 SQL。若只想审阅脚本,可在向导中选择查看、保存或导出 SQL 的选项,不要在尚未确认目标环境时直接执行。

生成前后都应确认目标 MySQL 版本和模型使用的语法一致。尤其检查以下内容:

  • 是否包含预期的数据库、表、列、主键和外键。
  • 字符串字段的字符集和排序规则是否符合项目约定。
  • 外键动作、唯一约束、检查约束和索引是否生成。
  • 表和列的命名是否需要引用,引用符是否为 MySQL 使用的反引号。
  • 是否存在工具自动添加但项目不需要的默认值或索引。

Forward Engineer 是把模型转换为目标数据库操作的过程;它不等同于安全、无风险的发布流程。若目标数据库已有同名对象,向导可能报错、要求调整或根据选项执行改动。生产环境中应在代码审查、备份、测试环境验证和迁移计划完成后,再按团队发布流程执行。

六、可运行的 MySQL 建表示例

以下 SQL 可在新建的 MySQL 8.0 数据库中执行。它表达了上面的核心结构。为避免不同工具生成的约束名称差异,示例显式命名主键和外键。若在已有数据库中运行,应先确认同名表不存在;CREATE TABLE 默认不会替你合并或改造已有表。

CREATE DATABASE IF NOT EXISTS shop_demo
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

USE shop_demo;

CREATE TABLE users (
  id BIGINT NOT NULL AUTO_INCREMENT,
  email VARCHAR(254) NOT NULL,
  display_name VARCHAR(100) NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uq_users_email (email)
) ENGINE=InnoDB;

CREATE TABLE products (
  id BIGINT NOT NULL AUTO_INCREMENT,
  sku VARCHAR(64) NOT NULL,
  name VARCHAR(200) NOT NULL,
  current_price DECIMAL(12, 2) NOT NULL,
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uq_products_sku (sku),
  CONSTRAINT chk_products_current_price
    CHECK (current_price >= 0)
) ENGINE=InnoDB;

CREATE TABLE orders (
  id BIGINT NOT NULL AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  order_no VARCHAR(40) NOT NULL,
  status VARCHAR(20) NOT NULL DEFAULT 'pending',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uq_orders_order_no (order_no),
  KEY ix_orders_user_created (user_id, created_at),
  CONSTRAINT fk_orders_user
    FOREIGN KEY (user_id) REFERENCES users (id)
    ON UPDATE RESTRICT
    ON DELETE RESTRICT
) ENGINE=InnoDB;

CREATE TABLE order_items (
  id BIGINT NOT NULL AUTO_INCREMENT,
  order_id BIGINT NOT NULL,
  product_id BIGINT NOT NULL,
  quantity INT NOT NULL,
  unit_price DECIMAL(12, 2) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uq_order_items_order_product (order_id, product_id),
  KEY ix_order_items_product (product_id),
  CONSTRAINT chk_order_items_quantity
    CHECK (quantity > 0),
  CONSTRAINT chk_order_items_unit_price
    CHECK (unit_price >= 0),
  CONSTRAINT fk_order_items_order
    FOREIGN KEY (order_id) REFERENCES orders (id)
    ON UPDATE RESTRICT
    ON DELETE CASCADE,
  CONSTRAINT fk_order_items_product
    FOREIGN KEY (product_id) REFERENCES products (id)
    ON UPDATE RESTRICT
    ON DELETE RESTRICT
) ENGINE=InnoDB;

INSERT INTO users (email, display_name)
VALUES ('[email protected]', '示例用户');

INSERT INTO products (sku, name, current_price)
VALUES ('BOOK-001', '数据库设计入门', 59.90);

INSERT INTO orders (user_id, order_no)
VALUES (1, 'ORD-2025-0001');

INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES (1, 1, 2, 59.90);

SELECT
  o.order_no,
  u.email,
  p.name,
  oi.quantity,
  oi.unit_price
FROM orders AS o
JOIN users AS u ON u.id = o.user_id
JOIN order_items AS oi ON oi.order_id = o.id
JOIN products AS p ON p.id = oi.product_id
WHERE o.order_no = 'ORD-2025-0001';

其中 uq_order_items_order_product 限制同一订单中的同一商品只出现一行。如果业务允许同一商品以不同规格、批次或折扣拆成多行,应调整唯一键,例如把规格标识也纳入唯一组合,或者移除该唯一约束并采用合适的明细标识。

ix_orders_user_created 面向按用户查询订单并按创建时间筛选或排序的常见场景。索引列顺序会影响索引可用于哪些查询条件;这不是放上去就一定提升所有查询的通用配置。实际项目应根据查询模式和执行计划决定。

七、用客户端验证结构

执行建表 SQL 后,可通过以下查询检查表、列和约束。不同 MySQL 版本对系统目录视图的具体字段可能略有差异;以下查询使用常见的 information_schema 视图。

USE shop_demo;

SHOW CREATE TABLE order_items;

SELECT
  TABLE_NAME,
  COLUMN_NAME,
  COLUMN_TYPE,
  IS_NULLABLE,
  COLUMN_KEY,
  COLUMN_DEFAULT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
ORDER BY TABLE_NAME, ORDINAL_POSITION;

SELECT
  TABLE_NAME,
  CONSTRAINT_NAME,
  CONSTRAINT_TYPE
FROM information_schema.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = DATABASE()
ORDER BY TABLE_NAME, CONSTRAINT_NAME;

检查结果时,不要只确认“表创建成功”。还要验证可空性、默认值、字符集、索引顺序和外键动作。可以尝试插入一条无效明细,确认数据库是否拒绝数量为零;也可以尝试插入不存在的 user_id,确认外键确实生效。

八、关键边界:图能表达什么,不能表达什么

关系图不等于业务规则全集

ER 图擅长描述实体、属性、基数和引用关系。它通常不能完整表达跨多张表的复杂业务规则、审批流程、权限逻辑、状态转换或并发下的操作语义。约束能在数据库层表达的,尽量用主键、唯一键、外键、非空和检查约束保障;复杂规则则需要应用逻辑、事务设计或其他机制,并通过测试验证。

模型与数据库可能不完全同步

开发过程中,团队可能有人直接执行 SQL、有人通过迁移工具改表,也有人只更新 Workbench 模型。时间久了模型就可能和真实数据库分叉。应明确模型是权威来源还是参考文档,并把变更纳入版本控制和代码审查。若模型为权威来源,每次结构变更都应同步更新模型;若迁移脚本为权威来源,则应定期核对模型与实际结构。

生成语句不等于迁移方案

首次建库时,生成完整的 CREATE TABLE 脚本通常比较直接。对已运行的数据库,结构变更涉及旧数据、锁表、回填、回滚和应用兼容性。生成一份新的建表脚本,不会自动替你制定这些步骤。生产变更应采用团队认可的数据库迁移机制,先评估数据影响,再在近似生产的数据环境中演练。

数据库方言和版本会影响结果

MySQL、PostgreSQL、SQL Server 等数据库在类型、引用符、索引、序列、自增列、检查约束和函数语法上并不相同。同一张抽象 ER 图并不保证可无损生成所有数据库的 SQL。切换目标数据库时,要重新审核数据类型、默认值、约束和迁移策略,而不是只替换连接地址。

九、常见错误与排查方式

1. 关系画对了,但外键列类型不匹配

常见现象是生成或执行时提示外键无法建立。检查两侧列的数据类型、长度、符号属性、字符集(字符串外键场景)以及被引用列是否有合适键。父表和子表的整数类型虽都叫 BIGINT,有符号与无符号差异也可能导致不兼容。

2. 只画关系线,没有确认外键是否生成

画布上的连线是模型表示,不足以证明最终 SQL 包含外键。检查表的 Foreign Keys 面板和 Forward Engineer 输出;执行后用 SHOW CREATE TABLE 核对。也要确认模型中的外键没有被导出选项排除。

3. 把所有字段都设为非空,或全部允许为空

过度严格会导致正常流程无法写入;过度宽松则会积累语义不明的空值。逐字段回答两个问题:业务上是否允许未知或暂缺?创建记录时是否一定能提供?注意 SQL 中 NULL 表示缺失或未知,不等同于空字符串、零或“无”。

4. 把级联删除当作默认值

级联能减少清理子记录的代码,但也会让一次父记录删除扩散到大量数据。先判断数据是否应该保留、是否有审计要求、是否需要恢复能力,再配置删除规则。对用户和订单等有历史价值的数据,常见方案是限制物理删除或使用状态字段。

5. 金额使用浮点类型

FLOAT 和 DOUBLE 采用近似表示,不适合要求精确十进制金额计算的场景。示例用 DECIMAL,但精度与小数位仍要根据币种、计价单位和业务规则选择。涉及多币种时,通常还要保存币种代码,并明确定价、舍入和汇率规则。

6. 忽略索引和唯一性设计

没有索引可能让关联查询变慢;索引过多则增加写入和存储成本。唯一性应由数据库约束保障,而不仅是应用先查询再插入,因为并发请求可能同时通过预检查。应根据真实访问模式选索引,并在数据量和查询计划上验证效果。

7. 认为 CHECK 约束在所有 MySQL 版本中行为一致

较早版本的 MySQL 对检查约束可能只解析而不执行;MySQL 8.0.16 起才按文档执行 CHECK 约束。若要兼容较早版本,不能假设示例中的数量和金额检查一定会生效,应确认目标版本并采用可行替代方案。工具的模型里显示了约束,也不代表目标服务器会执行它。

8. 在生产环境直接运行向导生成的脚本

生成 SQL 后先审阅,再在隔离的开发或测试环境执行。检查执行影响、是否存在破坏性语句、是否会改变已有数据,以及失败后的恢复办法。数据库工具不会知道当前库是否有业务高峰、备份是否可恢复或下游服务是否依赖旧结构。

十、安全与操作注意事项

ER 图和建表脚本本身不具备安全认证能力,也不会自动保护数据库。安全性来自数据库账户权限、网络访问控制、凭据管理、备份与审计等整体措施。创建模型时不要把真实密码、访问令牌或其他凭据写进备注、默认值或示例数据。

生成或执行脚本时,优先使用权限最小化的账户:仅授予当前任务需要的建库、建表或变更权限,避免在日常开发和自动化流水线中使用高权限管理员账户。生产环境执行需要遵循审批、备份和变更管理流程。限制权限不能替代 SQL 审查,备份也不能替代恢复演练。

如果工具连接真实数据库,应确认连接的主机、端口、库名和账户,避免把测试操作执行到生产库。不要把连接配置或含敏感数据的模型文件提交到公开仓库。团队共享模型时,可在提交前检查是否包含内部主机名、数据库名称或不必要的样例数据。

十一、推荐的完整工作流

  1. 整理需求:列实体、字段、关系和业务约束,标记不确定事项,不急于直接画图。
  2. 建立模型:在 Workbench 中创建表和字段,明确主键、可空性、唯一性及类型。
  3. 连接关系:建立外键,逐一检查基数、外键位置和删除更新策略。
  4. 评审结构:检查命名、字段含义、历史数据保留、索引和多对多关系拆分方式。
  5. 生成 SQL:通过 Forward Engineer 预览或导出脚本,核对目标服务器版本和实际语句。
  6. 隔离验证:在开发或测试数据库执行,检查约束、插入失败行为和常用查询。
  7. 版本管理:保存模型和经过审查的迁移脚本,明确哪一个是结构变更的权威记录。
  8. 发布与核验:按迁移流程实施,确认结果并记录变更;必要时准备回滚或前向修复方案。

结语

数据库设计工具能把表结构和关系直观呈现出来,也能减少重复编写建表语句的工作。有效的流程不是“画完图就上线”,而是先梳理业务约束,再建立模型、生成 SQL、检查数据库方言与版本,并在隔离环境验证。尤其要认真对待外键动作、唯一约束、索引和生产变更:这些选择会影响数据完整性、查询性能和历史记录是否可恢复。把模型、脚本和迁移流程纳入一致的团队规范,ER 图才会成为可维护的设计资产,而不只是一次性的示意图。

© 版权声明

相关文章