上周帮朋友整理一个老项目的数据库文档,他甩过来一个几百行的sql建表脚本,说“帮我看看这系统里到底有哪些表,表之间什么关系”。我第一反应不是打开文件从头读到尾,而是先把sql转成er图。做DBA和做后端的朋友应该都有同感:一张清晰的ER图,比盯着几十张建表语句靠脑子拼关系要靠谱得多。所谓sql转er图,就是把建表SQL、建索引SQL、外键约束这类数据库脚本自动或半自动地转成实体关系图,让表、字段、主外键关系一眼能看清。这篇文章就是把这类需求从零到落地,从工具到脚本,从踩坑到技巧,完整地掰开讲一遍。不管你是数据库管理员、后端开发、刚学数据库的学生,还是需要给旧系统写文档的人,读完就能自己动手做出来。
1. 为什么要把SQL转成ER图:应用场景与核心价值
1.1 看代码不如看图:ER图解决的核心痛点
一段建表SQL里其实藏着完整的数据结构信息,比如这张表叫什么、有哪些字段、主键是什么、哪些列有索引、哪些列通过外键关联到别的表。但这些信息是“线性排列”的,你得一条条语句扫过去,在脑子里把分散的表一个个拼起来。表少还好,一旦超过二十张,人的短期记忆就扛不住了。
ER图把这种线性信息变成空间结构信息。表是方框,字段写在方框里,主外键关系用连线表达,一对多、多对多一眼就能看出来。这个转换解决的不只是“好看不好看”的问题,而是把理解成本从“记忆”降到了“观察”。你不需要背下订单表里哪个字段指向用户表,图上一根线就说明白了。
另外,ER图还是很好的沟通工具。开发跟业务确认需求,画图比贴代码高效;新同事入职,给ER图比甩文档高效;系统重构,先画出现状ER图再谈改哪张表,比直接改代码稳妥。可以说,sql转er图这个过程本身,就是把“机器能读的东西”变成“人能看懂的东西”。
1.2 几类人最需要SQL转ER图
这个词虽然简单,背后对应的用户群体却挺杂的,不同人要做这件事的动机也不太一样。
- 后端开发:接手历史代码时,面对十几个迁移脚本、几百张表,最需要先画出一张总览图,搞清楚核心业务表和边缘表的关系,再考虑动代码。
- DBA / 数据运维:做数据库巡检、数据归档、分区方案时,需要快速知道哪些表有外键依赖,删数据、缩容、迁移时按什么顺序操作不会破坏引用完整性。
- 数据分析师 / BI工程师:写报表SQL之前,必须知道事实表和维表怎么join。手头没有现成数据字典时,把初始化SQL转成ER图是最快的摸底方式。
- 学生 / 面试准备:做课设、准备面试,经常需要讲解数据库设计。一份自己生成的ER图贴到文档里,比纯文字描述“用户表、订单表、商品表”清晰得多。
- 文档维护人员:老系统通常没有数据字典,只有一排建表脚本。把脚本转成ER图,再配上字段注释,就是一份可交付的数据库设计文档。
站在这些人的角度看,sql转er图不是炫技,而是实实在在的生产力工具。接下来要聊的是怎么把这件“生产力工具”快速落地。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. SQL转ER图的四条技术路线怎么选
2.1 路线A:直接连数据库,靠图形客户端导出
如果你手上不是SQL脚本,而是一个能连上的数据库实例,那最快的方式是直接让数据库客户端帮你画。mysql workbench、navicat、dbeaver这些工具都有实体关系图功能,连上库之后一键生成。
这个路线的核心逻辑是:数据库本身就在维护元数据,表结构、字段类型、主键、外键、索引都在系统表里。客户端从元数据里读出来,再按图的方式画出来。优点是很准,不会出现解析脚本时的各种意外;缺点是必须能连上数据库,而且外键约束如果没建,工具就只能画出表,画不出关系连线。
2.2 路线B:解析SQL脚本,不依赖数据库
老项目最常见的情况是数据库实例已经不在了,只剩一个初始化SQL文件。这时候要转ER图,就得靠解析建表语句。解析的思路很直接:把SQL文本按分号拆成一条条语句,识别CREATE TABLE、PRIMARY KEY、FOREIGN KEY这些关键词,把表名、字段、主外键抽出来,再交给绘图工具画。
这个路线不依赖某个数据库版本,也不依赖数据库账号权限,只要SQL文本是标准的建表语法,基本都能处理。缺点是实现起来比用现成工具麻烦一些,而且要处理各种方言语法,比如MySQL的`符号、AUTO_INCREMENT、COMMENT、ENGINE这些,PostgreSQL和SQL Server的写法又不一样。
2.3 路线C:在线工具快速出图
如果不想装软件,也不想写代码,可以把SQL脚本粘贴到在线工具里直接生成ER图。这类工具通常会解析SQL并自动排版,生成结果还能导出成图片或PDF。比较常见的包括dbdiagram.io、sqldbm、drawsql等。
在线工具的优势是零安装、上手快,适合临时画一张图;劣势是可能有数据隐私顾虑,内网环境、生产环境数据库结构不建议直接往第三方网站传。另一个问题是自定义能力有限,表多的时候布局往往不够理想,连线交叉、字段挤在一起都常有。
2.4 路线D:自建解析脚本
最后一条路线是自己写解析器。这种方式最适合两类场景:一是SQL脚本数量大、格式乱、工具解析不了;二是需要把ER图嵌入到公司内部的自动化工具体系里,比如每次发布后自动生成最新表结构图。
自己写的解析器,本质上是按你的业务规则去理解SQL文本。你可以判断哪些字段像外键、哪些表是日志表不需要出图、哪字段名后缀是“_id”就应该连到对应表上。这种灵活性是通用工具给不了的。代价是需要花时间和精力维护,但核心解析逻辑通常几百行代码就能搞定。
| 路线 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 图形客户端 | 能连上数据库 | 准确、省事 | 依赖连接权限,缺外键时无连线 |
| 解析SQL脚本 | 只有建表SQL文件 | 独立、可离线 | 需要处理方言语法 |
| 在线工具 | 临时、单次需求 | 快、零安装 | 安全隐患、布局不可控 |
| 自建脚本 | 批量、自动化、定制 | 自由度高 | 需要开发与维护 |
实际的建议是:第一次做,先用现成工具跑通流程,感受ER图需要包含哪些信息;再做深入处理,写脚本解决工具搞不定的部分。下面两章把这两条路分别展开讲。
3. 最快的路线:用现成工具5分钟生成ER图
3.1 MySQL Workbench:从SQL脚本直接生成
MySQL Workbench支持两种方式生成ER图,一种是连上数据库后反向工程,另一种是直接导入SQL文件。我平时用得最多的是“Create EER Model from SQL Script”,因为它不要求你事先建好库。
具体操作步骤大概是这样的:
- 打开MySQL Workbench,在首页选择“Create New EER Model”,或者“File -> New Model”。
- 在模型页面里,选择“File -> Import -> Reverse Engineer MySQL Create Script”。
- 选择你的SQL文件,点击Run,工具会自动解析建表语句、索引、外键。
- 解析完成后,进入ER图设计界面,选择“Arrange -> Auto Layout”让工具自动排版。
- 最后通过“File -> Export”导出成图片或PDF。
这里有个容易踩的坑:如果SQL脚本里包含CREATE DATABASE / USE语句,Workbench有时候会解析失败,或者把表建到了默认schema下面。解决办法很简单,先用文本编辑器把CREATE DATABASE和USE两行删掉再导入。另外,MySQL Workbench对SQL Server、Oracle这类非MySQL方言的支持基本为零,所以它只适合MySQL / MariaDB脚本。
3.2 DBeaver / Navicat:连接数据库导出
DBeaver和Navicat都是连库工具,生成ER图的逻辑也类似:连上数据库后,在数据库列表里选中某个schema,右键或双击表列表,找到“ER Diagram”或“View Diagram”入口。
以DBeaver为例,操作路径是:左侧数据库导航树展开目标数据库,右键选择“ER Diagram”。它会把当前库下的所有表、视图、外键关系都画出来。表多的时候可以在ER Diagram视图里用筛选功能只保留部分表,这个比Workbench灵活。
Navicat则在“模型”模块里,新建模型后,从“表”列表中选择需要的表拖到画布上,它也会自动识别外键关系生成连线。Navicat的好处是布局调整直观,支持一键自动排版,表之间可以手动调整位置,适合用来整理报告。
这两个工具的问题也一样:外键约束缺失就画不出连线。很多老系统在业务层维护关联,比如订单表里有个user_id字段,但根本没有加FOREIGN KEY约束,工具只能画出两张孤立的表。这种情况就需要回到脚本解析的路子上,靠字段名去推断关系。
3.3 在线工具来处理dbdiagram,导出好渲染
dbdiagram.io是我用过比较舒服的在线工具,它支持的语法很接近人写的自然语言,也因此很多人直接用它来“画”ER图,而不是从SQL生成。但如果你的SQL是标准MySQL语法,也可以直接粘贴进它的SQL Import功能里。
用dbdiagram.io的流程很简单:
- 打开dbdiagram.io,新建一个Diagram。
- 把SQL建表脚本粘贴到左侧编辑区,它会自动解析出Table定义。
- 如果解析不了,就改成DBML语法,比如:
code复制Table users {
id int
name varchar
}
Table orders {
id int
user_id int
}
Ref: orders.user_id > users.id
其实DBML就是dbdiagram自己的建图语言,它比SQL更直观,把“外键关系”用Ref这种显式方式写出来。如果SQL脚本本身没有外键,可以快速补几行Ref就可以把关系连起来。
在线工具的优势是浏览器打开就能用,方便临时演示;劣势是复杂SQL支持度有限,有些字段类型、多列主键容易解析出错。如果只是临时用一下,可以先用它跑通确认关系没问题,再导出SQL脚本给Workbench等工具做最终排版。
4. 自己写脚本:从原始SQL中提取表、字段与关系
4.1 思路拆解与整体流程
当你手里的SQL脚本格式乱、表数量多、外键缺失,又希望往自动化方向走的时候,自己写脚本是绕不开的选择。其实思路并不复杂,核心就三步:拆语句、抽结构、建关系。
- 拆语句:把整个SQL文件按分号拆成单条语句,去掉注释,分别处理CREATE、ALTER、DROP等操作。
- 抽结构:从CREATE TABLE语句中提取表名、字段名、字段类型、默认值、注释、主键信息;从ALTER TABLE中提取外键约束。
- 建关系:如果SQL里定义了FOREIGN KEY,直接从约束里拿到父表、子表、关联字段;如果没有外键,就通过字段命名规则(比如xxx_id)去推断,生成候选关系。
流程上,拆语句是第一步也是最重要的一步。很多SQL文件里分号不只是语句结尾,存储过程、函数、触发器里都会有分号,简单按split(";")切会切碎半个过程体。所以更稳妥的做法是用专业的SQL解析器,比如Python的sqlparse,它能识别语句边界,尽量不被存储过程干扰。当然,对于纯建表脚本,简单的split也能凑合用。
4.2 用 Python + sqlparse 解析建表语句
Python处理这类文本解析是最顺手的。sqlparse是一个轻量级的SQL解析库,不依赖数据库就能把SQL拆成语句。安装命令也简单:
bash复制pip install sqlparse
下面这段代码是我常用的解析骨架,它能把建表语句里的表名、字段、主键提取出来:
python复制import sqlparse
from sqlparse.sql import Identifier, IdentifierList
from sqlparse.tokens import Keyword, Name
def extract_create_tables(sql_text):
statements = sqlparse.parse(sql_text)
tables = {}
for stmt in statements:
cleaned = stmt.strip()
if not cleaned:
continue
# 只处理 CREATE TABLE,跳过 CREATE INDEX / CREATE VIEW
if cleaned.keywords and cleaned.keywords[0].value.upper() not in ('CREATE',):
# 通过 token 判断
pass
first_keyword = cleaned.token_first(skip_cm=True)
if first_keyword is None or first_keyword.value.upper() not in ('CREATE',):
continue
# 找到 TABLE 关键字后的下一个 [token](https://taotoken.net?utm_source=general),即为表名
ttype = None
name = None
for token in cleaned.tokens:
if token.value.upper() == 'TABLE':
ttype = token
continue
if ttype is not None and token.ttype in Name:
name = token.value.strip('`')
break
if name is None:
continue
tables[name] = {'columns': [], 'primary_key': []}
# 在括号内解析字段定义
in_paren = False
depth = 0
for token in cleaned.tokens:
if token.value == '(':
depth += 1
in_paren = True
continue
if token.value == ')':
depth -= 1
continue
if in_paren and depth == 1:
# 每个逗号分隔一段,就是字段名
if token.value == ',':
continue
# 这里简化处理,实际要拼接被逗号分隔的字段段
# ... 继续解析具体字段
return tables
这个代码只是示意骨架,真正做的时候不要直接照抄,而是要理解它的关键环节:sqlparse把语句切成token流,每一个建表语句的token里包含“CREATE”、“TABLE”、“表名”、“左括号”、“字段定义”、“右括号”。你只要定位到表名和括号的边界,括号内部再按逗号切分成字段定义。
实际项目里我会用更简化的方式,直接用正则匹配字段行:
python复制import re
FIELD_PATTERN = re.compile(
r'^\s*`?(\w+)`?\s+'
r'([a-zA-Z0-9()]+)' # 类型
r'(.*?)$', re.IGNORECASE
)
这种正则对于创建表字段行很管用,能提取出字段名和类型。但遇到括号有嵌套(比如varchar(50)里的括号)、带引号默认值、多行字段时容易出错。所以我的建议是:先用sqlparse拿到建表语句的完整文本,再针对字段区域用正则做精细提取,两层配合比单一工具可靠。
4.3 提取外键关系与JOIN推断
外键有两种定义方式,一种是在建表语法里直接写CONSTRAINT ... FOREIGN KEY,另一种是后期ALTER TABLE ADD CONSTRAINT。解析的时候要同时处理这两种写法。
如果是建表语法内部的,典型匹配规则是:
python复制FK_PATTERN = re.compile(
r'FOREIGN KEY\s*\(([^)]+)\)\s*REFERENCES\s+`?(\w+)`?\s*\(([^)]+)\)',
re.IGNORECASE
)
用这条正则去匹配表定义片段,能拿到子表字段、父表名、父表字段。比如:
sql复制CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES users(id)
这段会匹配出子表字段user_id、父表字段id、父表名users。
但如果脚本里完全没写外键,就得靠字段命名去猜。最常用的一条规则是:如果某个表里存在一个字段,名字是“其他表名_id”或者“其他表名单数_id”,则推测这个字段是指向那个表的主键。举个例子,orders表里有个user_id,users表有id,那大概率orders.user_id关联users.id。
当然,这类推断有误判可能。比如一张表里同时有editor_id和creator_id,都指向users表,你可能需要建两条关系线。还有一些业务字段,如created_by记录创建人,本质上也指向用户表,但你未必想在ER图上体现。所以推断逻辑应该做得保守一些,最好把候选关系放到一个列表里,人工确认后再画图。
4.4 生成Graphviz DOT文本并渲染
把表和关系抽出来之后,下一步就是画图。我不建议自己在HTML里画连线,直接用Graphviz的DOT语言表达关系,然后渲染成图片,最省事。
Graphviz是个老牌开源图可视化工具,用文本描述节点和边,它能自动做布局。DOT语法很简单:
dot复制digraph ER {
rankdir=LR
node [shape=plaintext]
users [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td bgcolor="#cccccc"><b>users</b></td></tr>
<tr><td>id (PK)</td></tr>
<tr><td>name</td></tr>
</table>
>]
orders [label=<
<table border="0" cellborder="1" cellspacing="0">
<tr><td bgcolor="#cccccc"><b>orders</b></td></tr>
<tr><td>id (PK)</td></tr>
<tr><td>user_id (FK)</td></tr>
</table>
>]
orders:user_id -> users:id
}
生成DOT文本之后,用命令行就能渲染图片:
bash复制dot -Tpng input.dot -o output.png
Graphviz支持HTML-like label,所以能在节点里画出表格形状,每个字段占一行,主键标上PK,外键标上FK。表之间的关系线用->,数据库的ER图通常习惯用连线加箭头表达“多对一”,这里也能直接表示。
这个方案的好处是脚本化程度高,可以批量生成几十个域模型图。缺点也很明显:Graphviz自动布局在表非常多的时候,连线交叉严重,需要靠rankdir、组(cluster)这些技巧去优化布局,后面在常见问题里会展开说。
5. 完整实操:一个真实建表脚本转ER图的全过程
5.1 准备样例SQL脚本
为了把前面讲的流程落地,我准备一个小型电商系统初始化脚本,包含用户、商品、订单、订单明细四张表。脚本里故意加了些注释和一些非标准写法,模拟真实场景。
sql复制CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARSET utf8mb4;
USE shop;
DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
DROP TABLE IF EXISTS users;
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '用户名',
`email` varchar(100) DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
CREATE TABLE `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL COMMENT '商品标题',
`price` decimal(10,2) NOT NULL DEFAULT 0.00,
`stock` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`total_amount` decimal(10,2) NOT NULL,
`status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '订单状态',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
CREATE TABLE `order_items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`quantity` int(11) NOT NULL DEFAULT 1,
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`),
CONSTRAINT `fk_items_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';
注意,这里我只在order_items上明确写了外键,orders表里的user_id和order_items里的product_id都没有外键约束。这是很多真实老系统的常态,恰恰也是我们做ER图时最需要补全的部分。
5.2 预处理:去注释、统一格式、处理分号
拿到SQL脚本,不要直接扔给解析工具,先做几步预处理能省很多麻烦。
第一步,去掉文件里的注释。有些脚本注释是--开头的,有些是#开头的,也有/*...*/块注释。解析工具一般能忽略一些注释,但为了减少干扰,我习惯先清掉。正则表达式可以简单处理:
python复制import re
def strip_sql_comments(sql_text):
# 去掉 -- 和 # 单行注释
sql_text = re.sub(r'--.*', '', sql_text)
sql_text = re.sub(r'#.*', '', sql_text)
# 去掉 /* */ 块注释(非贪婪)
sql_text = re.sub(r'/\*.*?\*/', '', sql_text, flags=re.S)
return sql_text
第二步,把反引号去掉。MySQL脚本里到处是`,解析时可以统一替换成空字符串,但对SQL Server来说中括号[]才是标识符,所以这一步要按数据库方言来。
第三步,把CREATE DATABASE和USE语句删掉或忽略。前面说过,这类语句对很多ER转换工具不友好,如果只是做表结构分析,直接用正则过滤掉即可。
第四步,处理分号。如果是纯建表脚本,一个完整语句结尾就是分号,简单split(";")就行。如果脚本里包含存储过程、触发器,就需要用sqlparse这种智能解析工具,避免把函数体里面的分号误当成语句结束。
5.3 解析结果:抽取表、字段、主外键
按照第4章的解析逻辑,对上面的样例脚本,我们期望拿到的结果大致是这样的:
- 表 users:字段id(PK), name, email, created_at;无外键。
- 表 products:字段id(PK), title, price, stock;无外键。
- 表 orders:字段id(PK), user_id, total_amount, status, created_at;无外键约束,但存在字段user_id。
- 表 order_items:字段id(PK), order_id(FK), product_id, quantity;外键order_id -> orders.id。
解析脚本输出可以用JSON保存,方便后续生成图时使用:
json复制{
"orders": {
"columns": {
"id": {"type": "int", "primary_key": true},
"user_id": {"type": "int"},
"total_amount": {"type": "decimal(10,2)"},
"status": {"type": "tinyint"},
"created_at": {"type": "datetime"}
},
"relations_out": [
{"field": "user_id", "ref_table": "users", "ref_field": "id", "inferred": true}
]
},
"order_items": {
"columns": {
"id": {"type": "int", "primary_key": true},
"order_id": {"type": "int"},
"product_id": {"type": "int"},
"quantity": {"type": "int"}
},
"relations_out": [
{"field": "order_id", "ref_table": "orders", "ref_field": "id", "inferred": false},
{"field": "product_id", "ref_table": "products", "ref_field": "id", "inferred": true}
]
}
}
能看到,orders.user_id和order_items.product_id都被标记成inferred: true,说明这是根据字段命名推断出来的关系,而不是真实外键约束。这种标记方式很重要,后面的图可以据此用不同线型或颜色区分真实外键和推断关系。
5.4 生成并微调ER图
有了结构化数据,生成DOT文本就只是拼字符串的功夫。我可以把每一张表渲染成一个表格节点,字段行里区分主键、外键、普通字段,然后关系连线从子表的外键字段指向父表的主键字段。
生成DOT后,先跑一次Graphviz看效果。以我这套脚本为例,最初自动布局出来的图,order_items和orders靠得比较近,products和users被拉得比较远,整体还算能看。要是表数量到几十张,就需要调整布局方向(比如rankdir=LR改成TB,即从左到右改为从上到下),或者用subgraph把同域的表圈在一起。
微调的时候有两个小技巧比较实用:一是在DOT里手工指定某些表的rank,让相关的表处在同一水平线上,连线会短很多;二是把外键关系用不同颜色标注,比如真实外键用实线、推断外键用虚线,看图的人一眼就知道哪些关系是代码里写死的,哪些是分析推测的。这个细节在给团队讲解时特别有用,能避免误导。
6. 常见问题与排查技巧实录
6.1 建表脚本没有外键,怎么补关系
这是所有SQL转ER图实践中遇到最多的问题。很多系统在设计时为了性能或者开发便利,把外键约束省掉了,只在应用层通过ORM维护关系。面对这种脚本,工具只能画出孤立表。
我的做法是:先按字段名规则自动生成候选关系,再人工确认。字段名规则主要有几种:
- 子表字段名是父表名加
_id,比如orders.user_id -> users.id。 - 子表字段名是父表名单数加
_id,比如order_items.product_id -> products.id。 - 父表名本身是复数,子表字段用单数,比如customers表,子表用customer_id。
自动推断之后,最好根据业务常识再筛一遍。比如有个字段叫created_by,它确实指向users表,但这类“操作人”字段在ER图上画出来会让图变得很乱。我通常会把这类字段单独放一个列表,先不参与连线,只在需要详细设计评审时补上。这样保证ER图首先是可读的,而不是信息全得没法看。
6.2 字段注释、类型、自增主键丢失怎么办
有些解析工具只能识别简单的CREATE TABLE结构,遇到COMMENT、AUTO_INCREMENT、DEFAULT CURRENT_TIMESTAMP这类MySQL特性,容易忽略或者解析失败。这时候一是要确认工具支持什么方言,二是检查自己的脚本是不是包含了不被支持的语法。
如果是自己写脚本解析,出现这类问题大多是因为解析维度不够细。比如字段名后面跟着的不只是类型,还有varchar(50)和NOT NULL DEFAULT 'xxx',中间还有空格和括号嵌套。我的建议是对于字段行,采用“先定位字段名,再取到行尾或逗号前的部分作为属性块”,属性块内部可以用正则按关键字继续细分。只要保证表名、字段名、外键关系这三大关键信息不丢,其他属性(类型、备注、默认值)丢失还可以接受。真要保留注释,就要对COMMENT 'xx'做专门的提取,很多DBA画ER图其实很在意注释,因为最终交付的文档里需要字段说明。
6.3 表太多没法看:分域与分组
SQL转ER图的最终产出如果是一张上百张表的大图,基本等于没画,因为没人能看清。解决思路是分层、分域。
我见过比较靠谱的做法是:第一层是全库总览图,只显示表名,不显示字段,连线上标注关系数量;第二层按业务域拆成子图,比如用户域、订单域、商品域,每个域包含十几张表,显示核心字段;第三层是单表详情,列出所有字段、索引、注释。做SQL转ER图的时候,尽量让脚本生成的结果结构化,便于落入这种分层体系,而不是一次性画一大张。
Graphviz里可以用subgraph实现分组。例如把users和orders相关的表圈在“user_order_domain”里,配合cluster的样式,输出图上会有个虚线框,阅读体验会好很多。
6.4 SQL转ER图常见问题速查表
| 问题 | 原因 | 解决办法 |
|---|---|---|
| 工具自动生成的图没有关系连线 | 建表脚本没有FOREIGN KEY | 按字段命名规则推断关系,人工确认后补画 |
| 在线工具解析MySQL脚本报错 | 工具只支持标准SQL | 先删掉反引号、ENGINE、COMMENT等MySQL特有语法 |
| 解析结果字段丢失 | 注释或默认值里包含逗号、括号 | 使用sqlparse解析语句边界,正则只负责字段级提取 |
| 生成的ER图里表过多 | 一次性把整个库画出来 | 分域、分组、按业务模块拆图 |
| 关系连线交叉严重 | 布局算法未按业务分组 | 使用rankdir、subgraph、手工调整rank |
| 视图/存储过程出现在图里 | 解析脚本没过滤非表对象 | 只处理CREATE TABLE,跳过CREATE VIEW/PROCEDURE |
| 中文注释乱码 | 文件编码和工具默认编码不一致 | 统一转成UTF-8,导入前检查文件编码 |
最后说点自己的体会。我第一次做sql转er图,用的是最笨的办法,手工画了十几张表的关系,画完还漏了三个外键。后来改成脚本优先、工具辅助的思路,效率提升非常明显。工具能解决80%的常规场景,但遇到字段命名不规范、外键缺失、同名字段这种老系统遗留问题,脚本加人工微调才是可靠的组合。建议你接到这类需求时,不要直接打开软件就导,先花两分钟确认手头是建表SQL脚本还是数据库连接权限,再决定走哪条路线。这个思路反过来也成立:你在设计新系统时,把ER图当成数据库设计的源头,把建表脚本当成ER图的产物,整个结构会清晰很多。
