现代数据库还需要设计范式吗?
什么是范式
在设计关系数据库时,为了减少数据冗余和提高数据一致性而遵循的一系列规则和标准。
范式的基本原则
- 保证列的原子性.
- 通过对表进行人工的笛卡尔积分解来得到最少的数据耦合.
概览
优点
- 数据极度的不冗余, 存储占用较低
- 指导了如何切分数据以及构造
缺点
- 数据非常难构造, 需要用到大量的join.
- 性能差
- 不符合直觉
现代计算机系统如何进行数据库设计
在现代计算机系统中,数据库范式设计仍然是一个重要的概念,但它的应用需要根据具体的业务需求和性能要求进行权衡。
范式设计的必要性
数据库范式设计的主要目标是减少数据冗余和提高数据一致性。通过将数据拆解为原子表,范式设计可以有效地防止数据的重复和更新异常。例如,通过第三范式(3NF),我们可以确保一个表中的非主键字段不依赖于另一非主键字段,进而避免冗余数据的存储。
然而,过度的范式化带来的问题也非常明显。在性能敏感的场景下,过度的范式化会导致以下问题:
- 复杂的查询:多表连接(JOIN)操作会变得复杂且耗时,影响查询性能。
- 维护困难:表结构过于复杂,维护和理解成本增加。
减少数据耦合的两种方式
-
设计范式:
- 优点:减少数据冗余,提高数据一致性,数据更新时不会产生异常。
- 缺点:表结构复杂,查询性能可能较差,尤其是在需要频繁进行多表连接的情况下。
-
领域驱动设计 (DDD) :
- 优点:通过逻辑上划分数据的作用域,定义领域(Bounded Context),在领域内允许一定的反范式设计,从而简化表结构和查询操作。
- 缺点:需要更复杂的领域建模和设计,领域之间的数据协调可能需要额外的机制。
数据冗余的可接受性
在现代计算机系统中,数据冗余在某些场景下是可以接受的,尤其是在性能敏感的应用中。具体原因如下:
- 性能优先:在实时性要求高的系统中,例如金融交易系统、电商系统等,查询性能至关重要。适度的数据冗余可以减少复杂的表连接,提升查询速度。
- 存储成本降低:存储成本的下降使得适度的数据冗余在经济上变得可行。以往为了节省存储空间而极力避免的数据冗余,现在可以通过增加存储空间来解决。
- 业务需求:在实际业务中,有时需要快速响应查询请求,甚至在业务逻辑上存在特殊需求,这时适度的数据冗余可以简化查询逻辑,提高系统的响应速度。
考虑因素
- 业务需求:根据具体的业务需求来决定数据的范式化程度。在一些关键业务场景下,允许适度的数据冗余以简化查询和提升性能。
- 性能要求:对于性能敏感的系统,应尽量减少复杂的多表连接,适度增加数据冗余以提高查询速度。
- 维护成本:过度的范式化可能增加系统的维护成本,因此需要在范式设计和实际维护之间找到平衡点。
- 领域驱动设计:引入领域驱动设计思想,合理划分领域,减少领域之间的耦合,允许领域内适度的反范式设计,以提高系统的整体性能和可扩展性。
小结
在现代计算机系统中,虽然范式设计依然重要,但在实际应用中,应根据具体的业务需求和性能要求进行权衡。适度的数据冗余在很多场景下是可以接受的,尤其是在性能敏感的系统中。
数据库的六种范式
第一范式 (1NF)
定义
第一范式要求数据库表中的每一列都是原子的,即每个字段只包含单一值,不允许重复的组或多值属性。
示例
假设我们有一个表 students
,记录学生的信息:
不符合第一范式的表结构:
students
-------------------------------
| student_id | name | subjects |
-------------------------------
| 1 | John | Math, Science |
| 2 | Jane | English |
| 3 | Bob | Math, History |
-------------------------------
在这个表中,subjects
列包含了多个值,这不符合第一范式。
符合第一范式的表结构:
students
--------------------
| student_id | name |
--------------------
| 1 | John |
| 2 | Jane |
| 3 | Bob |
--------------------
student_subjects
------------------------
| student_id | subject |
------------------------
| 1 | Math |
| 1 | Science |
| 2 | English |
| 3 | Math |
| 3 | History |
------------------------
在这个表结构中,students
表和 student_subjects
表都只包含单一值的字段,符合第一范式。
第二范式 (2NF)
定义
第二范式要求在满足第一范式的基础上,表中的每个非主键字段都完全依赖于主键,而不是部分依赖于主键。即如果一个表的主键是由多个字段组成的组合键,那么表中的非主键字段必须依赖于整个组合键。
示例
假设我们有一个表 course_enrollments
,记录学生选课的信息:
不符合第二范式的表结构:
course_enrollments
------------------------------------------------------
| student_id | course_id | course_name | grade |
------------------------------------------------------
| 1 | 101 | Math | A |
| 1 | 102 | Science | B |
| 2 | 101 | Math | A |
| 3 | 103 | History | B |
------------------------------------------------------
在这个表结构中,course_name
依赖于 course_id
,而不是整个组合键 (student_id
, course_id
)。
符合第二范式的表结构:
course_enrollments
---------------------------
| student_id | course_id | grade |
---------------------------
| 1 | 101 | A |
| 1 | 102 | B |
| 2 | 101 | A |
| 3 | 103 | B |
---------------------------
courses
----------------------
| course_id | course_name |
----------------------
| 101 | Math |
| 102 | Science |
| 103 | History |
----------------------
在这个表结构中,course_name
被移到 courses
表中,course_enrollments
表中的非主键字段都完全依赖于主键。
第三范式 (3NF)
定义
第三范式要求在满足第二范式的基础上,表中的非主键字段之间没有传递依赖关系。即一个非主键字段不能依赖于另一个非主键字段。
示例
假设我们有一个表 employees
,记录员工的信息:
不符合第三范式的表结构:
employees
----------------------------------------------
| employee_id | name | department_id | department_name |
----------------------------------------------
| 1 | John | 10 | HR |
| 2 | Jane | 20 | IT |
| 3 | Bob | 10 | HR |
----------------------------------------------
在这个表结构中,department_name
依赖于 department_id
,而 department_id
是一个非主键字段。
符合第三范式的表结构:
employees
-----------------------
| employee_id | name | department_id |
-----------------------
| 1 | John | 10 |
| 2 | Jane | 20 |
| 3 | Bob | 10 |
-----------------------
departments
-------------------------
| department_id | department_name |
-------------------------
| 10 | HR |
| 20 | IT |
-------------------------
在这个表结构中,department_name
被移到 departments
表中,employees
表中的非主键字段之间没有传递依赖关系。
BC范式 (BCNF)
定义
BC范式要求在满足第三范式的基础上,表中的每个决定因素都是候选键。即如果一个表中存在非主键字段决定其他字段的情况,那么这个非主键字段必须是候选键。
示例
假设我们有一个表 project_assignments
,记录员工分配到项目的信息:
不符合BC范式的表结构:
project_assignments
---------------------------------------
| employee_id | project_id | manager_id |
---------------------------------------
| 1 | 101 | 201 |
| 2 | 101 | 201 |
| 3 | 102 | 202 |
---------------------------------------
在这个表结构中,manager_id
依赖于 project_id
,而 project_id
不是候选键。
符合BC范式的表结构:
project_assignments
---------------------------
| employee_id | project_id |
---------------------------
| 1 | 101 |
| 2 | 101 |
| 3 | 102 |
---------------------------
projects
------------------
| project_id | manager_id |
------------------
| 101 | 201 |
| 102 | 202 |
------------------
在这个表结构中,manager_id
被移到 projects
表中,project_id
是候选键。
第四范式 (4NF)
定义
第四范式要求在满足BC范式的基础上,表中不存在多值依赖。即一个表中的字段不能同时依赖于两个独立的多值属性。
示例
假设我们有一个表 students_courses
,记录学生的选课和活动信息:
不符合第四范式的表结构:
students_courses
------------------------------------------
| student_id | course_id | activity_id |
------------------------------------------
| 1 | 101 | 501 |
| 1 | 102 | 502 |
| 2 | 101 | 503 |
------------------------------------------
在这个表结构中,course_id
和 activity_id
是独立的多值属性。
符合第四范式的表结构:
students_courses
---------------------------
| student_id | course_id |
---------------------------
| 1 | 101 |
| 1 | 102 |
| 2 | 101 |
---------------------------
students_activities
---------------------------
| student_id | activity_id |
---------------------------
| 1 | 501 |
| 1 | 502 |
| 2 | 503 |
---------------------------
在这个表结构中,students_courses
和 students_activities
分开存储,避免了多值依赖。
第五范式 (5NF)
定义
第五范式要求在满足第四范式的基础上,表中的数据不能通过更小的表来无损分解。即确保表中的数据不能通过分解成更小的表来表示,而不丢失信息。
示例
假设我们有一个表 students_courses_teachers
,记录学生选课和授课教师的信息:
不符合第五范式的表结构:
students_courses_teachers
-------------------------------------
| student_id | course_id | teacher_id |
-------------------------------------
| 1 | 101 | 301 |
| 1 | 102 | 302 |
| 2 | 101 | 301 |
-------------------------------------
在这个表结构中,student_id
和 course_id
以及 course_id
和 teacher_id
之间存在多值依赖。
符合第五范式的表结构:
students_courses
---------------------------
| student_id | course_id |
---------------------------
| 1 | 101 |
| 1 | 102 |
| 2 | 101 |
---------------------------
courses_teachers
---------------------------
| course_id | teacher_id |
---------------------------
| 101 | 301 |
| 102 | 302 |
---------------------------
在这个表结构中,students_courses
和 courses_teachers
表分开存储,确保表中的数据不能通过分解成更小的表来表示,而不丢失信息。