君哥聊技术

面试官:什么是数据库范式?什么是反范式?有什么优缺点?

大家好,我是君哥。

设计数据库表结构时,数据库范式是我们经常考虑的策略。但往往很多时候,我们会违反范式,以获得更好的性能。今天来聊一聊这个话题。

1.范式

第一范式

表中字段是原子的,不可以再拆分。一个经典的例子就是地址字段记录了省市区:

id
地址
1
北京市海淀区

如果要符合范式,可以对地址进行拆分。

id
省
市
区
1
北京市
北京市
海淀区

当然,这里只是为了举例说明,实际项目中,会使用标准的行政地区码来进行保存。

第二范式

首先要满足第一范式,在此基础上主键以外的字段必须完全依赖主键,不能依赖其他字段。比如下面的订单表:

订单id
产品id
产品数量
购买价格
订单金额
100
p201
2
20
130
100
p202
3
30
130

订单表如果单独用“订单id”不能标记唯一记录,只能使用“订单id+产品id”做联合主键,但是订单金额这个字段跟“产品id”没有关系,这就不符合第二范式。可以对订单表进行优化,去除“订单金额”:

订单id
产品id
产品数量
购买价格
100
p201
2
20
100
p202
3
30

订单金额表:

订单id
订单金额
币种
100
130
RMB

查询的时候使用订单id 做 JOIN 查询。

第三范式

首先要满足第二范式,在此基础上,表中的字段不能间接依赖主键,也就是需要通过依赖传递来对主键形成依赖。再看下面的订单表,主键是订单id:

订单id
用户id
用户姓名
100
C10
tom
101
C11
json

用户姓名不能直接依赖主键“订单id”,而是通过“用户id”间接依赖的,这就不符合第三范式。可以拆分出用户表,查询的时候使用用户id 进行 JOIN。 订单表:

订单id
用户id
100
C10
101
C11

用户表:

用户id
用户姓名
C10
tom
C11
json

BCNF 范式

也称 BC 范式,它是在满足第三范式的基础上,不允许一个表中存在两个可以做主键的字段。比如下面这个仓库表:

仓库id
管理员id
存储商品id
存储商品数量
S100
M10
P200
200
S100
M10
P300
230

如果一个仓库只能由一个管理员管理,而一个管理员也只能管理一个仓库,那主键可以选择{仓库id,存储商品id},也可以选择{管理员id,存储商品id}。这就不符合 BCNF 范式。 可以把上表拆分出两个表,仓库管理表:

仓库id
管理员id
S100
M10
S100
M10

仓库表:

仓库id
存储商品id
存储商品数量
S100
P200
200
S100
P300
230

4NF 范式

4NF 范式是在第三范式的基础上,不允许表中字段由多对多的依赖。比如下面这个表符合第三范式,但是订单id 和产品id 存在多对多的关系。

订单id
产品id
产品名称
100
p201
产品1
100
p202
产品2

可以拆分成三个表,订单产品关系表:

订单id
产品id
100
p201
100
p202

订单表:

订单id
订单金额
100
130

产品表:

产品id
产品名称
p201
产品1
p202
产品2

查询的时候做三张表的 JOIN 查询。

2.优缺点

从上面范式的介绍可以看出,范式是数据库设计的规约,主要有以下优点:

  • 减少存储空间:同一个实体的属性只在表中存储一次,减少了数据冗余,节省了存储空间;
  • 写数据性能高:每个属性的插入、更新通常只需要操作一张表,操作的数据集小,效率更高;
  • 数据完整性:遵守范式设计,表中需要保存关联实体的主键,通过主键关联来保证数据完整性。
  • 去重操作少:没有冗余数据,也就很少会用到类似 distinct 和 group by 这样的耗时语句。

但过度遵循范式设计,也会存在一些缺点:

  • 查询性能受到影响:查询通常需要 JOIN 多个表,JOIN 的表数量较多时,JOIN 语句会成为性能瓶颈;
  • SQL 语句复杂度升高:多张表 JOIN 往往使查询语句可读性差,遇到重构、迁移之类的工作,会带来很多额外工作量;
  • 对索引依赖更多:为了提高 JOIN 语句性能,往往需要在连接字段上建立索引。

因为遵循范式可能存在的缺点,在实际设计和开发中,我们往往会引入反范式,通过增加数据冗余,将不遵循范式但是需要的字段放到一个表中,通过增加冗余来避免复杂的 JOIN,通过对冗余字段增加索引来提高查询效率。核心思想也是时间换空间。

反范式带来的优点是简化查询语句,提高 SQL 执行效率,对并发读的场景更加合适。

但在写多的场景下,也会存在一些问题,比如因为要写多张表,增删改操作更复杂 ,很容易造成锁竞争,降低写入性能。同时也更容易导致数据不一致,维护难度增加。

3.使用建议

在我们的实际项目开发中,一般都使用混合范式。

对 OLTP(联机事务处理)类型的使用场景,比如电商、ERP 等写入比较多的系统,可以考虑使用范式设计。

而对于 OLAP(联机分析处理)的使用场景,比如报表、数仓等,需要处理复杂查询,都是读取操作,可以考虑采用反范式设计。通常使用 ELT 工具把业务数据从关系型数据库抽取到湖仓,在湖仓构建反范式化的数据模型,用于业务数据查询和报表生成。

精品专栏 70 篇,推荐阅读。
又老性能又差,为什么好多公司依然选择 RabbitMQ?

45 个知识点,带你入门消息队列!

引入了 Disruptor 后,系统性能大幅提升!

 从 MySQL 迁移到 GoldenDB,上来就踩了一个坑。

面试官:MySQL BETWEEN AND 语句包括边界吗?

面试官:MySQL Redo Log 和 Binlog 有什么区别?分别用在什么场景?

面试官:使用 MySQL 时你遇到过哪些索引失效的场景

面试官:MySQL表中有2千万条数据,B+树层高是多少?

面试官:MySQL JOIN 表太多,你有哪些优化思路?

感谢阅读,如果对你有帮助,请点赞和在看。欢迎加我微信:zhujinjun86。

号内回复 seata,下载《阿里分布式中间件Seata从入门到精通》

号内回复 beijing,下载我总结的北京上百家知名科技公司

号内回复 aqs,下载《40张图精通Java AQS》