你真的懂关系型数据库吗?从数学关系到MySQL设计

发布时间:2026/9/7 21:34:51
你真的懂关系型数据库吗?从数学关系到MySQL设计 我一直觉得“关系型数据库”这五个字是计算机行业里最容易被忽略的专业术语之一。大家天天用MySQL写SELECT写JOIN写到手软表结构设计也聊得头头是道可要是哪天真有人问一句“为什么它叫关系型数据库”很多人第一反应大概率是——关系是不是指表跟表之间能通过外键关联起来这个理解不能算错但离本质还差得很远。我之所以想写这个话题是因为之前带过几个刚入门的朋友发现大家普遍卡在一个点上SQL语法背得很熟建表、查询都挺溜但一旦遇到“为什么主键不能为空”“为什么这表要拆成两张”“为什么JOIN这么慢”这类问题就只能靠经验硬记没法从原理上推导。而这些问题的答案恰恰都藏在对“关系”这个词的理解里。这篇文章不会讲什么高深理论而是把一个天天都在用的概念掰开揉碎关系到底从哪来、关系模型约束了什么、SQL和关系代数是什么关系、以及理解了这些之后你再回头看MySQL里的表、约束、锁、事务会发现很多所谓“背下来”的知识其实都是可以自己推出来的。1. “关系”这个词从一开始就不是你理解的那个意思1.1 一个天天写SQL的人为什么会突然卡壳先别急着说自己懂。我问过不少开发经验三五年的人“关系型数据库的‘关系’指的是什么”十个人里大概有七八个会回答“就是表之间可以通过外键关联起来嘛一对一、一对多、多对多那种。”剩下两三个人会说“关系型数据库嘛就是有行有列的那种表结构数据库。”这两个回答都是日常语境里的“关系”但都不是数据库理论里“关系”的本意。要搞清楚这件事得回到四十多年前。1970年IBM的研究员Edgar F. Codd发表了一篇论文题目叫《A Relational Model of Data for Large Shared Data Banks》中文一般翻译成《大型共享数据库的关系数据模型》。这篇论文被公认为关系型数据库的理论起点。Codd在论文里做了一个非常关键的决定他用数学里的“关系”Relation这个概念来给数据建模。数学里的“关系”是什么如果你上过离散数学应该还有印象给定几个集合比如集合A和集合B那么A×B的笛卡尔积里的一堆有序对的子集就叫A到B的一个二元关系。举个朴素例子集合{张三李四}和集合{语文数学}的笛卡尔积是{(张三语文)(张三数学)(李四语文)(李四数学)}我从这里面取一部分比如{(张三语文)(李四数学)}这就是一个关系它表示“张三选修了语文”“李四选修了数学”这两条事实。Codd做的事情就是把这个数学概念直接搬到了数据管理领域把每个表看成是一个“关系”表里的每一行是一个“元组”每一列是一个“属性”表头给属性取名表体就是元组的集合。所以从理论源头上讲“关系”不是“表之间的关联”而是“一张表本身就是一个关系”。1.2 Codd的1970年论文和数学上的“关系”为什么Codd要引入这么数学化的概念因为在他之前主流的数据库模型是层次模型和网状模型。这两个模型有一个共同点数据之间的访问路径是预先设计好的你如果想查某个数据必须从根节点一层一层往下走或者沿着指针游历。这就导致一个问题——数据怎么存、怎么遍历跟业务逻辑死死绑在一起。今天你想换一种查询方式可能整个应用的访问路径都要改。Codd对这种“物理存储主导逻辑”的做法非常不满。他在论文里明确提出了一个思想数据管理不应该让用户关心数据是怎么存的而应该让用户在逻辑层面直接操作数据。数学里的“关系”恰好是个纯逻辑概念它不涉及指针、不涉及存储路径只关心数据本身的结构和集合运算。你把表看成关系然后用关系运算来查询剩下的事情交给系统去优化。所以“关系型数据库”里的“关系”本质上是在强调一种“基于集合、基于逻辑”的数学化数据组织方式。Codd论文里最著名的十二规则后来也成为关系型数据库的试金石规则里很多条都在说同一件事“数据库系统必须把存储表示和逻辑表示分开”“必须支持关系代数的各类操作”。这些规则正是从“关系”这个数学概念延伸出来的。1.3 为什么说“表”就是“关系”而不是“有关联的表”理解到这一层很多概念就能对上了为什么一张表叫“一个关系”为什么不把外键关联叫“关系”因为外键关联在关系模型里其实是一种数据约束叫“参照完整性”它并不是关系模型的基础而是为了保证数据正确性而加的一条附加规则。打个比方把数据库想象成一个大书架。层次模型和网状模型像是给书编了一棵树的索引每一本书的位置取决于它在树上的路径而关系模型则是把所有书平铺开每本书就是一条记录书之间有没有联系取决于你查的时候提出什么条件。外键约束只是其中一种“书的编号规则”保证你不会借到一本不存在的书。这个区别非常关键。它解释了为什么关系型数据库里表和表之间的“关系”可以在查询时动态建立——就是那句出现在SQL里的“ON users.id orders.user_id”。如果没有JOIN条件两张表完全可以各过各的而数学意义上的“关系”是指每一张表本身就是一个独立的、带结构的数据集合。说到这儿“关系型数据库”这个名字的来历算是讲清楚了。但光知道名字还不够真要理解MySQL为什么值得信赖得继续往下追关系模型究竟对表、行、列、键做了哪些具体约束这些约束又是怎么落实成MySQL的建表规则的。2. 关系模型到底约束了什么从建表到约束的底层逻辑2.1 一张合规的“关系表”长什么样关系模型的“关系”既然来自数学集合那它就对表有几个基本要求这些要求后来被总结成“关系模型的三大完整性”和“范式理论”。先看最表层的约束。第一每个关系都有一个名字也就是表名。关系名在同一个数据库里必须唯一否则没法区分。第二每个属性的域domain是确定的。域可以简单理解成“这个列允许取什么值”。比如age列域是整数name列域是字符串。MySQL里的数据类型就是域的落地实现。你可能会看到热搜词里有人问“mysql中int5是什么意思”这就涉及到MySQL里数值运算和类型转换的问题——如果你定义了一个INT类型的列那么它参与加法和比较运算时就会遵循整数域规则。域的概念还带来一个隐含要求同一列里的值应该是同一类型的不能这一行是数字下一行变成字符串。第三元组行是集合的元素所以行的顺序不重要。传统Excel里你可能很在意第3行是张三还是李四但关系模型里集合是无序的表的行顺序没有任何业务含义。这也是为什么你在MySQL里执行SELECT不带ORDER BY时返回顺序是不确定的——从关系模型的角度看这完全正常因为你查询的是一堆元组的集合而不是一堆有顺序的记录。第四每个元组必须能区分。集合里不允许有重复元素所以关系模型要求每个表至少要有一个候选键Candidate Key也就是一组能唯一标识一行的属性。这是后面讲主键的前提。把这些要求放到一块一张合规的关系表本质上就是一个“属性排列有序但行无序、行与行互不重复、每列类型固定”的二维集合。有人会说这不就是Excel吗区别挺大的。Excel允许单元格合并、允许列里混着不同类型、允许完全重复的行、还允许行列顺序有实际含义这些在关系模型里都是不允许的。用Excel久了再过来用MySQL之所以会觉得“被约束得很死”正是因为关系模型把逻辑规则定得非常彻底。2.2 主键、外键、唯一键三个最容易被轻视的完整性规则关系模型的三大完整性是Codd那篇论文里就明确提出来的MySQL里对应的实现就是主键、外键、唯一约束和NOT NULL约束。先看实体完整性Entity Integrity。规则很简单主键的值不能为空且表中所有行的主键值必须唯一。为什么因为主键就是“元组的身份标识”如果它是空的你没法判断这一行到底是谁如果允许重复那这个关系就退化成多集合了数学理论和集合运算就不成立了。MySQL里最容易踩的坑是很多人建表时不指定主键或者用一个业务字段当天主键比如身份证号、手机号。从关系模型的角度看这样不是不行但风险很大。业务字段能不能保证绝对非空、绝对不变、绝对唯一答案往往是不能。比如“手机号”可能因为注销被回收“身份证号”对海外用户可能为空。所以我一直建议表里要有一个与业务无关的代理主键比如自增ID或UUID。这不是MySQL的限制而是关系模型给你留的安全垫。再看参照完整性Referential Integrity。规则也很简单外键列要么为空要么必须引用到被引用表中真实存在的主键值。它的目的是保证“关系”之间的一致性。MySQL里对应FOREIGN KEY约束但在实际生产环境很多团队会故意不用外键。为什么因为外键会在每次INSERT和UPDATE时触发额外检查影响写入性能而且在分库分表场景下外键根本跨不了库。但不用外键不代表没有参照完整性需求——这是两码事。我在项目里见到的做法是应用层保证外键逻辑数据库只建普通索引。这样做性能更可控但代价是应用代码必须格外小心否则很容易产生“孤儿记录”。所以关键不是“用不用外键”而是你对“数据一致性由谁保证”要有明确方案。最后是用户定义完整性User-defined Integrity比如CHECK约束、默认值、唯一索引等。MySQL对CHECK约束的支持在8.0.16之后才真正生效之前你写了CHECK也可能被忽略。所以如果你想严格限制某列取值最好还是在应用层再做一次校验别把所有希望寄托在数据库上。2.3 用MySQL复现关系模型的完整约束一个订单系统的建表示例光讲理论容易飘我们来落地一个例子。假设要做一个最小化的订单系统有两张核心表用户表和订单表。用户表userCREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 代理主键, username VARCHAR(50) NOT NULL COMMENT 登录名, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里主键是自增的idusername加了唯一键。唯一键属于候选键的一种它保证了业务层面“一个登录名不能被注册两次”同时又允许主键和业务字段分离开。订单表ordersCREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 订单主键, order_no VARCHAR(32) NOT NULL COMMENT 订单号, user_id BIGINT NOT NULL COMMENT 下单用户, amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 订单金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付 2已取消, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES user (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意user_id我加了外键约束。这个设计的含义是不能插入一个user_id在user表中不存在的订单而且因为外键user表里要删除有订单的用户也会受限制。从关系模型的角度看这张表既满足了实体完整性主键非空且唯一又满足了参照完整性外键引用存在还通过ENUM类型或CHECK约束的替代方案status字段配合注释满足了一部分用户定义完整性。你会在设计中发现一个有意思的现象明确理解了“关系”之后你在建表时会自然而然地思考三件事——这张表的主键是什么、它引用了哪些表的主键、业务上哪些字段必须唯一。这三个问题想清楚了表结构基本不会出大乱子。当然现实世界里我们经常会为了性能去打破一部分规则比如去掉外键、允许冗余字段、增加反范式设计。这没问题但前提是你得知道自己在打破什么规则以及打破之后要用什么手段来兜底。不懂关系模型就直接反范式那是瞎搞懂了之后再反范式才叫权衡。3. SQL为什么能成为关系模型的“方言”关系代数到SELECT的映射3.1 关系代数那几个基本运算SQL里全都有Codd在论文里不但定义了关系模型还定义了一套对关系进行运算的数学语言这就是关系代数。关系代数是SQL的“前身”也是SQL能成为一门声明式语言的底层基础。关系代数里有八个基本操作选择SELECT、投影PROJECT、连接JOIN、并UNION、交INTERSECT、差MINUS、笛卡尔积PRODUCT、除DIVISION。你不用背这些名字只要注意到一个规律关系代数的每个运算输入是一个或多个关系输出仍然是一个关系。这意味着你可以把运算结果再套进下一个运算里无限组合。SQL之所以能写嵌套子查询、能JOIN完再GROUP BY再ORDER BY就是这个“闭包”特性在起作用。用MySQL语法对应一下选择SELECT注意这里的SELECT是关系代数里的σ选择不是SQL关键字选出满足条件的行对应SQL里的WHERE。投影PROJECT选出指定列对应SQL里的SELECT column1, column2。连接JOIN把多个关系按条件组合成新关系对应SQL里的JOIN ... ON ...。并UNION两个关系上下拼在一起对应SQL里的UNION。差MINUS从第一个关系里去掉第二个关系也有的行对应SQL里的NOT IN或LEFT JOIN ... WHERE ... IS NULL。有一个挺有意思的地方关系代数的“选择”在SQL里变成了WHERE但SQL的SELECT关键字其实对应的是“投影”。很多初学者会被这个名词混淆绕晕但这恰恰说明了SQL和关系代数是一一映射的关系。理解这层映射后写SQL不再是背语法而是变成做“关系运算”。3.2 一条SELECT语句是怎么被优化器演绎成关系的你要是以为SQL是直接一行行执行到数据库里的那就想简单了。MySQL拿到一条SQL之后第一件事是解析、验证、生成语法树然后交给优化器Optimizer去生成执行计划。优化器做得最有意思的一件事就是利用关系代数的等价变换来改写查询。举个例子SELECT u.username, o.order_no FROM user u JOIN orders o ON u.id o.user_id WHERE o.amount 100这条SQL在关系代数层面可以有两种执行思路思路A先把user和orders做笛卡尔积CROSS JOIN再按u.id o.user_id过滤再按o.amount 100过滤最后投影出username和order_no。思路B先在orders表上做amount 100的选择再按user_id去和user表做连接最后投影。显然思路B的中间结果小得多执行效率也高得多。优化器的工作就是找到这种等价改写把大关系先变小再去做连接。这就是你经常听说的“谓词下推”“连接顺序优化”。那这和“关系型数据库为什么叫关系型”有什么关系关系大得很正因为关系代数是封闭在“关系”这个数学结构上的所以这些等价变换才总能保持语义不变。系统可以放心大胆地重排运算顺序因为不管怎么变最终结果都仍然是同一个“关系”。如果换成一个没有严格数学模型的存储系统优化器根本不敢乱动你的执行步骤。我之前调试一条慢查询时就是靠这个思路解决的先把大表的过滤条件提前、把能缩小的子查询尽可能下推让优化器减少中间结果集。很多人说“MySQL优化器傻”其实更多时候是SQL写得不够“关系化”——你让它在早期就不得不处理一个巨大的中间关系神仙也优化不动。3.3 那些“不像关系运算”的SQL存储过程、函数、触发器的定位有人可能会问关系代数是纯集合运算但MySQL里的存储过程、函数、触发器这些玩意儿看起来跟集合运算半毛钱关系都没有它们是不是“非关系”的这个观察很敏锐。严格来说存储过程、函数、触发器确实是关系模型之外的过程性编程扩展。Codd的理想是让用户只做声明式查询所有逻辑都用关系运算表达。但现实世界的业务逻辑太复杂有些东西用纯SQL集合运算写起来很别扭比如循环、条件分支、异常处理。MySQL里写存储过程也确实是很多开发者的痛点。热搜词里就有人搜索“mysql存储过程”“mysql中触发器中分隔符”说明大家在实战中会碰到。比如用存储过程批量更新订单状态DELIMITER $$ CREATE PROCEDURE batch_update_order_status( IN p_user_id BIGINT, IN p_status TINYINT ) BEGIN UPDATE orders SET status p_status WHERE user_id p_user_id AND status 0; END$$ DELIMITER ;注意这里用了DELIMITER $$因为存储过程里面的;会被MySQL命令行客户端当成语句结束符所以需要临时换一个分隔符。这是MySQL特有的头疼之处。但从关系模型的角度看存储过程、函数、触发器都属于“数据库编程扩展”它们是SQL的补充不是关系模型的核心。关系模型的核心永远是两件事表关系 关系运算SQL。存储过程这类东西更像是数据库这个“关系计算器”上额外加的快捷按钮方便你封装复杂的多步骤逻辑但它们本身并不属于Codd当年那个“关系”的范畴。理解了这一点你就不容易走偏能用一条关系运算SELECT/JOIN/子查询解决的就别写一堆过程性代码存储过程写多了反而会让业务逻辑变得难测试、难维护。我自己处理复杂查询时会先试着用纯SQL表达实在绕不过去才考虑用函数或存储过程兜底。4. “关系”是优势也是代价MySQL在某些场景下为什么会别扭4.1 关系模型换来的东西一致性、事务与成熟度关系模型给数据库带来的最大红利是逻辑层与管理层的分离。用户不需要关心数据是存在B树叶子节点还是堆表里不需要管索引是不是覆盖只要拿SQL去查询剩下交给系统。这份“抽象红利”让关系型数据库在过去五十多年里统治了几乎所有核心业务系统。紧接着的第二个红利是事务与并发控制。关系模型强调数据完整性而完整性的保证离不开事务。MySQL里最常用的InnoDB引擎提供了ACID事务原子性、一致性、隔离性、持久性。你可能已经发现ACID里的C一致性和关系模型里的完整性约束是一脉相承的——事务保证数据库从一个一致状态转移到另一个一致状态而一致状态就是“所有主键非空唯一、所有外键合法、所有CHECK约束满足”的状态。这就是为什么在银行转账、库存扣减、订单支付这些场景里大家本能地会选MySQL而不是别的存储。不是因为它性能有多逆天而是它把“数据可靠”这件事从根上设计进了模型里。你在MySQL里执行UPDATE account SET balance balance - 100 WHERE id 1InnoDB会通过行锁、MVCC、redo log、undo log来保证这条更新要么成功要么影响为零不会出现“扣了钱但余额没变”的中间态。热搜词里有个高频词是“mysql锁表”很多新手在线上遇到锁等待就慌。但从关系模型的角度看锁机制恰恰是保证关系完整性的必要代价。没有锁事务之间的写操作就会互相覆盖外键约束和唯一约束也没法在执行过程中稳定发挥作用。所以当你面对锁等待问题时不该只是一味追求“不锁”而是要想清楚你的事务到底需要哪些关系上的约束能不能通过缩小事务范围、优化索引来降低冲突。4.2 规范化设计的代价当JOIN成为瓶颈关系模型的第三个遗产是规范化理论。为了减少数据冗余、避免更新异常我们会把一张大表拆成多张小表用外键关联起来。这就是所谓的“范式设计”。规范化的确让数据存储变得干净但也带来了一个很现实的成本查询时要把多张表重新拼回去而拼表就是JOIN。JOIN看似简单背后却是关系到性能的运算。拿MySQL最常见的嵌套循环连接Nested Loop Join来说如果驱动表有N行被驱动表有M行最坏情况下要做N次索引查找。驱动表越小被驱动表索引命中率越高JOIN就越快。但如果你在每张表上只建了主键索引没有针对连接列建索引那查询就会演变成全表扫描乘以全表扫描的组合慢到怀疑人生。我之前接手过一个报表项目业务表冗余设计很差一次报表查询要JOIN七张表每张表都几十万行。性能压测的时候一条查询直接跑了十几秒。后来做的优化不是什么高深招数而是先按关系模型重新梳理了表结构把不必要的JOIN去掉再给所有连接列和过滤列建上合适的普通索引最后把查询从十秒压到了一秒内。这就是“关系”的代价表拆得越规范查询时拼装的成本越高。所以DDL阶段就要在“规范”和“查询需求”之间做取舍。规范化和性能之间不存在绝对答案但有三个经验可以分享核心业务表优先保证规范化因为数据准确性大于查询便利性。报表、统计类表可以用反范式设计冗余存字段减少JOIN。如果一定要JOIN确保连接列上有索引并控制驱动表的数据量。4.3 NoSQL的“反关系”思路到底反掉了什么既然关系模型有成本那网上铺天盖地的NoSQL是来干嘛的很多人以为NoSQL是“不要SQL”但其实NoSQL更像是“Not Only SQL”它的核心是反关系模型。关系模型要求你先定义好表结构然后把数据塞进一个严格的集合结构里。这被称为“schema-on-write”也就是写入时就要确定字段类型和约束。而像MongoDB这样的文档型数据库允许你往同一个集合里写入字段完全不同的文档这是“schema-on-read”读取时才去解释数据结构。NoSQL反掉的第二个东西是关系运算的强制性。在MongoDB里两个集合之间的关联通常靠应用层手动查两次或者在文档里内嵌子文档。它不要求你遵守实体完整性和参照完整性所以写入性能可以做到很高水平扩展也更方便。代价是一致性变弱了事务支持要么没有要么有限。那什么时候选NoSQL什么时候选MySQL我自己的判断标准很简单如果你的业务最看重的是数据之间的关系和一致性选关系型数据库如果你最看重的是写入吞吐和灵活的数据结构而且业务逻辑不强依赖跨实体关联NoSQL可以考虑。比如存储用户会话、埋点日志、商品快照这些用文档型数据库很合适但涉及订单、支付、库存、账户这种强约束强一致性的场景老老实实用MySQL别拿核心数据去赌NoSQL的灵活性。这里忍不住多说一句很多人一说“MySQL不够用”第一反应是换数据库。但换数据库从来不是解决性能问题的最佳起点。真正该做的是先审视你用的关系模型是否合理、索引是否到位、SQL是否写成了大规模关系运算、是否可以把部分查询迁移到缓存或搜索引擎。把MySQL用到极限远比换来换去有价值。5. 回到面试和实际设计理解了“关系”之后很多题都好答了5.1 常见面试题背后其实都在考关系模型热搜词里有“mysql面试题”我见过不少候选人在这种基础题上翻车事后一看其实都是对“关系”理解不到位。举几个高频问题你会发现它们有一条暗线。问题一什么是关系型数据库它和NoSQL的区别是什么标准答法不是背定义而是从三个层面说第一关系型数据库以二维表关系为基本存储结构表之间通过外键和JOIN表达业务关联第二关系型数据库遵循ACID事务保证数据完整性第三它使用SQL这门声明式查询语言底层由关系代数支撑。NoSQL则更多是schema-on-write的弱约束模型以最终一致性换取水平扩展能力。问题二MySQL里INNER JOIN和LEFT JOIN的区别底层是什么从关系代数看INNER JOIN是求两个关系的“交集连接”只返回两边都满足条件的行LEFT JOIN则是先做连接再把左边关系中没匹配上的行保留下来右边列置为NULL。理解这层后你就能解释“为什么LEFT JOIN后WHERE里加右表条件会变成INNER JOIN”——因为WHERE是在连接结果上过滤一旦加了右表条件NULL行就被过滤掉了等价于求交集。问题三为什么MySQL的COUNT(*)和COUNT(1)差别不大而COUNT(列)可能慢一些COUNT(*)在关系模型里是统计“元组的个数”COUNT(列)是统计“某列非空的元组个数”。MySQL 8.0之后COUNT(*)和COUNT(1)已经被优化器等价处理了基本没差别而COUNT(某列)因为有NULL判断优化空间略小。问题四MySQL里ORDER BY不走索引怎么办关系模型假设行的顺序无意义所以排序是查询阶段显式指定的操作。如果你需要经常按某列排序就要考虑建二级索引让B树的叶子节点天然有序避免filesort。这也是从“行无序”这个关系模型前提推导出来的。这些问题看着零散答案却都能从“表是关系、行是元组、SQL是关系运算”这几句话里推出来。所以我说理解了关系模型面试基础题根本不用背。5.2 范式推导一张乱表如何规范到第三范式范式理论是关系模型的核心应用之一也是面试喜欢问的“硬骨头”。但范式不是靠背定义而是可以一步步推出来。假设有一张员工绩效表字段是员工编号、员工姓名、部门编号、部门名称、职位、绩效等级。这张表有什么问题最明显的。部门名称依赖于部门编号而部门编号并不依赖员工编号本身而是依赖员工所待的部门。这就产生了“部分函数依赖”和“传递函数依赖”导致如果部门改名要改很多行如果一个部门暂时没有员工部门信息甚至存不进去。第一范式1NF要求所有列都是原子的即每列只能有一个值。这张表每个字段都是原子的所以满足1NF。第二范式2NF要求先满足1NF再要求非主键列完全依赖于主键而不是只依赖主键的一部分。如果这张表主键是员工编号那部门名称并不直接描述员工它描述的是部门所以它还依赖部门编号而部门编号完全可以独立成表。这里就存在“部分依赖”不满足2NF。第三范式3NF要求先满足2NF再要求非主键列之间不能有传递依赖。员工编号 → 部门编号 → 部门名称这就构成传递依赖不满足3NF。怎么改把部门拆出去员工表员工编号主键、员工姓名、部门编号、职位、绩效等级。部门表部门编号主键、部门名称。这样部门名称只存在于部门表中员工表通过部门编号和部门表关联查询时JOIN一下即可。这个拆分过程中你会发现“关系”模型里“每个表只描述一个实体”的原则体现得淋漓尽致。表就是关系一个关系应该聚焦一个业务对象而不是把所有信息揉在一起。做业务设计时我不建议一上来就把范式背得多熟而是这样问自己“这张表里有没有哪个非主键字段是在描述另一个对象的信息”如果有拆出去。这个习惯养成了范式基本不会出错。5.3 一个小技巧怎么向别人解释“关系型数据库”最后分享一个我自己用的解释框架。如果下次有人问我“MySQL为什么叫关系型数据库”我不会直接甩数学定义而是用三层递进第一层字面来源。关系这个词来自数学的RelationCodd把数据组织成“表”并规定每张表就是一个关系。这里的“关系”指的不是表和表之间能不能关联而是“一张表的本质是一个数学集合”。第二层结构特征。表由行和列组成行是集合里的元组列是属性所有数据都有明确类型和约束这种结构让数据表达变得非常规范。正因为它像集合所以所有操作都能用集合运算来描述。第三层操作方式。SQL是一种基于关系代数的声明式语言。你只要告诉数据库“我要什么”不用告诉它“怎么找”。系统会通过优化器在关系代数层面重写查询计划找到最优执行路径。这个框架既能用来回答问题也能用来审视自己的设计。我经常在代码评审时用这套思路去提建议你这个表是不是一个干净的“关系”你的查询是不是在做关系运算你和另一张表的关联是不是真有必要如果都能回答上来那MySQL在你手里基本就变成一把趁手的刀而不是一个天天报错的黑盒子。我在实际工作中还有一个很深的体会很多线上故障表面上看起来是“MySQL慢”“MySQL死锁”“MySQL锁表”根子上其实是表结构没按关系模型设计、SQL没按关系运算思维去写。把“关系”这个根基打牢比多背几条命令管用得多。而且这东西想忘都忘不掉——一旦你从集合、从关系代数的角度看MySQL再回头优化那些SQL你会发现以前要死记硬背的优化技巧都变成了顺理成章的选择。