MySQL 中的三大范式,不是死记硬背的教条,而是数据库表结构设计时减少冗余、避免异常、提升一致性的实用指南。它们层层递进,后一范式建立在前一范式已满足的基础上。
第一范式(1NF):字段必须是“不可再分”的最小单位
核心就是原子性——一个字段里只能存一个值,不能塞多个信息。比如把“北京朝阳区建国路8号”整个当地址存进一列,看起来方便,但没法按“城市”筛选、没法单独索引“区”,也不利于校验格式。
常见违反情况:
用逗号拼接多个电话:"13800138000,010-12345678"在一个字段里存多个兴趣爱好:
"篮球,阅读,编程"地址不拆分,统一写成文本
正确做法是拆成独立列或独立表。例如地址可拆为
province、
city、
district、
detail;多值兴趣则应建关联表
user_interests(user_id, interest)。
第二范式(2NF):所有非主键字段必须“完全依赖”整个主键
前提是已满足 1NF。关键在复合主键场景下防“部分依赖”。比如一张表主键是
(order_id, product_id),但
product_name只跟
product_id有关,跟
order_id毫无关系——这就违反了 2NF。
后果很实际:
数据冗余:同一商品名在不同订单里反复出现 更新异常:商品改名,得改几十行甚至几百行 插入异常:还没生成订单,但想先录入商品信息?不行,因为缺order_id
解法很简单:把只依赖部分主键的字段拎出去,单独建表。比如把商品信息放进
products(product_id, name, price),原表只留
order_items(order_id, product_id, quantity)。
第三范式(3NF):非主键字段之间不能有依赖关系
在满足 2NF 基础上,进一步禁止传递依赖。典型例子:用户表里存了
user_id、
city、
city_population。这里
city_population不是直接依赖
user_id,而是通过
city间接得到的——这就是传递依赖。
问题在于:
同一个城市人口重复存储多次 某城市人口变化,要批量更新所有该城市的用户记录 如果某个用户填了不存在的城市,人口字段就失去意义处理方式:把城市相关信息抽离,建
cities(city_name, population)表,用户表中只保留
city_name(或更好是
city_id),通过外键关联。
范式不是越高越好。3NF 是工程实践中最常采用的目标,BCNF 或更高阶范式在特定复杂业务中才考虑。有时为了查询性能,还会主动反范式化,比如冗余一个常用统计字段。关键是理解每一条规则背后想解决什么问题,而不是机械套用。
