数据表设计原则及三范式

数据表的几种的关系
  • 一对一:学生和学生证
  • 一对多:学生和班级
  • 多对多:学生和课程
如何表示数据库表之间的关系
  • 使用外键:数据库外键关系表示的其实是一种一对多的关系
  • 一对一:外键+唯一
  • 多对多:引入中间表,把一个多对多表示为两个一对多

# 表设计的三大范式

  所谓范式,即如何建立科学的,规范的的数据库,需要满足一些规范的来优化数据数据存储方式的指导办法。

第一范式(1NF)
  • 每一列属性都是不可再分的属性值,确保每一列的原子性
  • 合理的根据实际业务数据需求来决定属性,合并相似或相同的列,避免冗余

假如有一个业务需求是:需要了解一批用户的基础信息(包括姓名,年龄,电话,详细地址,以及根据地址明细进行分类),如果数据库表设计如下,则不满足第一范式:

不满足第一范式示例

  以上实例中设计了地址,如果需求根据进行分类则无法实现
【解决办法】根据需求适当的将地址拆分为更原子的省市两个属性即可符合第一范式

第二范式(2NF)
  • 需要确保数据库表中的每一列都和主键相关,如果是联合主键,则需要和所有主键均相关而不能只与主键和某一部分相关
  • 在一个数据库表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中

  假设有表如下,学号和课程编号是联合主键,但是,该表中,课程名称和学号没有任何关系,学生姓名和课程编号也没有任何关系

不满足第二范式示例

【解决办法】(1)提取学生表;(2)提取课程表;(3)建立学生课程关系表。
  值得注意的是,学生表只存储学生的基本信息属性;课程表只存储课程的相关属性;学生课程关系表,只存储学号(学生表主键)和课程编号(课程表主键)以及一些其他学生课程关系的属性字段,并不存储学生基本信息和课程相关信息,即可符合第二范式

第三范式(3NF)
  • 确保数据表一个记录中的数据都和主键直接相关,而不是间接相关,不能存在传递关系
  • 属性不依赖于其他非主属性

  假设有如下表,学号为主键,它存在 学号 --> 班级编号 --> 班级信息 这么一个主键学号与班级信息的传递关系,不符合第三范式

不符合第三范式示例

【解决办法】(1)提取学生表;(2)提取班级表;
  学生肯定在某一个班级中,所以班级编号可以作为学号(主键)的一个直接关联属性,但班级的其他信息应该放在以班级编号为主键的表中,即可符合第三范式。

遵循范式的优缺点

  通过以上的了解,可以发现,范式规则有如下特点

  • 结构合理,表含义容易理解及区分
  • 冗余较小
  • 但性能有所降低,多表查询比单表效率低下

  总结:数据库表的设计,可以借鉴三大范式的指导办法,同时也需要依赖于实际业务需求,良好的数据库结构不仅可以提高开发人员的开发效率,降低开发难度,还可以提高数据库查询效率,给程序增加可变弹性,当冗余的代价小于查询性能降低的代价时,就应该考虑冗余实现。


# 表设计的建议

1. 字段的原子性

  保证每列的原子性,字段含义言简意赅,高度概括。
  能用一个字段表达清楚的绝不使用第二个字段,须要使用两个字段表达清楚的绝不能使用一个字段

2. 主键设计

  主键不要与业务逻辑有所关联,最好是毫无意义的一串独立不重复的数字
  例如:使用UUID,或者将主键设置为AUTO_INCREMENT
  根据数据库设计三大范式,尽量保证列数据和主键直接相关而不是间接相关

3. 字段长度

  字段长度尽量要比实际业务的字段大3-5个字段左右(考虑到合理性和伸缩性)
  不要建比实际业务大太多的字段长度,,这是因为如果字段长度过大,在进行查询的时候索引在B-Tree树上遍历会越耗费时间,从而查询的时间会越久

4. 关于外键

  尽量不要建立外键,保证每个表的独立性。如果非得保持一定的关系,最好是通过id进行关联

5. 动静分离

  做好静态表和动态表的分离
  静态表:存储着一些固定不变的资源,比如城市/地区名/国家(静态表一定要使用缓存)。动态表:一些频繁修改的表

6. 关于Null值

  尽量不要有null值,有null值的话,数据库在进行索引的时候查询的时间更久,从而浪费更多的时间!可以在建表的时候设置一个默认值!

7. 预留表开关字段

  设计一个单一字段去控制表是否可用,如 isValid: true/false

8. 删除字段

  数据库是禁止使用delete命令的,一般都不会真正删除数据,都是采用改状态的方式,设置state字段,通过修改状态赋予它是否有效的逻辑含义!


# 表设计的几项原则

1. 职责分离原则

  在设计的时候应当考虑到数据的产生,聚合使用等原则,每个系统干自己能干的事情,每个系统只干自己的事情:明确如下几点:
(1)谁产生这个信息,就由谁对此数据负责
(2) 谁最经常使用这个信息,就由该系统来负责保存维护该数据
(3)遵守高内聚,低耦合的考虑:

2. 在线处理与分析分离原则

(1)为了保障线上数据处理的性能,将一些分析相关的数据及分析结果,应当使用单独的库来进行存储,避免在数据分析的时候导致业务数据吞吐量下降,引起系统问题。
(2)专门用于存放离线报表数据,并提供线上数据查询方法,建议将统计结果,汇总的数据都从在线处理数据库中移走。

3. 事务与日志分离原则

(1)用户生成内容和用户行为日志要分开。系统本身预设的数据,记录用户身份及与功能相关的数据,和用户的行为日志应当各自单独存储
(2)行为日志,需要做分析处理,并且由于时效性不宜存储在MySQL中,后期维护就是地雷。

4. 历史可追溯原则

  在数据库设计的时候为了保障数据是可追溯的,应当遵循一些简单的约定,事后方便数据的查询和统计:
(1)对于状态数据,应当设计相应状态的字段来保存该数据的最后状态,同时记录下来该数据的初始创建人,时间以及该数据的最后修改人和修改时间;
(2)针对需要跟踪每次修改的数据,需要在数据发生变化的时候记录一张日志表,用于记录该数据发生变化的全生命周期。针对只需要关注关键字段变化的情况,则日志表中只需要记录关键字段变化即可,但操作人,操作类型,时间应当准确记录,日志表数据一旦生成不允许进行修改。
(3)针对所有历史需要保留的数据则需要每次变化都生成一个新的版本,比如类目信息等,对原始数据永远只做insert操作,不做deleteupdate操作。但这种情况仅限于极端数据历史要求极高的情况下使用。