建表时需考虑的事情
在实际的业务场景中,利用关系表来构建整个业务需求,那么我们需要考虑的是
范式规范(1NF,2NF,3NF,BCNF,4NF,5NF);
表之间的关系(一对一,一对多,多对多);
表用途(数据表,配置表);
数据库级别配置
表字段
索引使用
范式规范
1NF:关系中的每个属性都不可再分。
1NF是所有关系型数据库的最基本要求,不符合1NF的关系表是无法在数据库中建立的。但是只满足1NF的表数据冗余过大,而且会存在插入异常,删除异常,修改异常的问题。我们举一个金融系统中贷款申请的一个实际业务场景来说,现在设计一个贷款申请表,该表符合1NF
数据冗余过大:贷款渠道描述,贷款产品描述,所属额度描述,这三个字段完全是冗余字段。
插入异常:如果我们想新增一个贷款渠道,但是没有人从这个渠道贷款,我们便无法插入这个渠道。
删除异常:我们要删除李四的贷款信息,那么对应的贷款产品p2,也被删除了。
修改异常:我们要修改张三的贷款产品的话,那么产品对应关系也被修改了。
总之一句话,1NF下的表,牵一发动全身。
2NF和3NF:消除了非主属性对于码的部分函数依赖(2NF)和传递函数依赖(3NF)
首先,理清概念
完全函数依赖:
部分函数依赖:
传递函数依赖:
码:设 K 为某表中的一个属性或属性组,若除 K 之外的所有属性都完全函数依赖于 K(这个“完全”不要漏了),那么我们称 K 为候选码,简称为码。在实际中我们通常可以理解为:假如当 K 确定的情况下,该表除 K 之外的所有属性的值也就随之确定,那么 K 就是码。一张表中可以有超过一个码。(实际应用中为了方便,通常选择其中的一个码作为主码)
表之间的关系
如果你设计的表符合BCNF范式的话,那么对于一对一,一对多,多对多这些表之间的关系就能理解了。之所以表之间有这些关系,就是为了让表符合范式规范。
一对一关系
实际情况中,我们只有在垂直分表中使用。表中字段数过多,可以分为热点字段和非热点字段,我们可以根据这个,利用主键,将这些字段拆分到不同的表中。
一对多关系
实现2NF和3NF规范,我们就要用到一对多关系。
多对多关系
要想实现多对多关系,就需要引入一张中间表,中间表中的两个字段存储另外两张表的主属性(主键)
表用途
数据表
数据表(流水表)是有状态的,多笔业务之间没有关联,会有大量insert,update和查询操作。是我们优化的重点对象。
配置表
一些业务数据的配置,特点是数据量较小,而且结构简单,一般为静态数据,变化频率很低,但CRUD的场景比较多。即使出现大量CRUD,但数据量小,其实影响不大。
其实配置表和数据表是一对多主外键关联关系。但我们会面临一个问题,就是配置表中的配置不是一成不变的,那这个时候数据表中外键关联的该项配置就跟着变了。其实到底变还是不变是根据实际业务需求来看的。根据后视镜原则,我思考了一个比较好的方案,能够满足表范式规范,又能回溯数据。
配置表:(id,code,version,status...业务相关配置)
其中id为表主键,唯一;code为业务编码,可以唯一标识一个业务配置;version为该业务版本的版本号(code+version为主属性,可以唯一标识一笔数据)stats为生效失效状态(code+status也为主属性,也可以唯一标识一笔数据)。当修改某一项配置的时候不进行update,而是insert一条新的数据,只是version+1,然后上一笔失效。
数据表:(id,foreign1,foreign2,...其他数据)
id为主键;对于外键关联配置表的foreign,我们到底选配置表的id,还是code+status(或者是code+version)?如果我们要求数据表中配置项不需要关联最新配置,而是保留当时配置现场数据,那么我们就选id为foreign;如果要求实时最新配置数据的时候,我们就要选择code+status为foreign。
数据库级别配置
存储引擎的选择
InnoDB 存储引擎支持事务,一般业务系统特别是交易类系统,都需要事务支持,所以只能选InnoDB。MyISAM特点是访问速度快,如果事务没有要求,但有大量insert和select操作,可以选择。
字符集选择
从应用(前后端),到数据库,我们都应统一字符集,这样可以避免字符集转化带来的性能损耗。推荐都使用UTF-8
事务隔离级别
MySql默认的隔离级别是Repeatable-Read,但建议改为Read-Committed,更适合OLTP 业务场景。
表字段
1.定长字符类型使用char(),但对于InnoDB存储引擎,使用varchar要更好。
2.变长字符类型使用varchar(),但对于变长字符应该有个预估,不能统一初始化为varchar(500)等。
3.整型定义中无需定义显示宽度,比如:使用 INT,而不是 INT(4)。
4.存储精确浮点数必须使用 DECIMAL 替代 FLOAT 和 DOUBLE。
5.尽可能不使用 TEXT、BLOB 类型,如果必须使用,建议将过大字段或是不常用的描述型较大字段拆分到其他表中;另外,禁止用数据库存储图片或文件。数据库处理 TEXT、BLOB 类型相当耗时,特别是进行删除操作。
6.日期类型选择,如果是年,用year(4);如果是年月日,用DATE;如果是年月日时分秒,用DATETIME。建议时间字段使用应用生成,而不是数据库。
7.建议字段定义为 NOT NULL。特别是索引列,因为MySQL对于null的列使得索引,索引统计和值的比较都更复杂。
8.每个字段都要有注释
索引使用
对于不同的存储引擎,支持的索引类型也不一样,现在只讨论InnoDB的B+树索引,全文索引,HASH索引。但由于90%的情况都是使用B+树索引,所以我们先讨论。
首先现解释几个概念
B+树:
是一个特殊的查找树,本文不展开篇幅进行解释,但可以去看程序员小灰讲解的B+树,里面讲解了什么是B+树,以及为什么数据库索引使用B+树。
聚集索引:
1.一张表有且只有一个聚集索引。
2.如果表有主键,按表的主键构造一个B+树,叶子节点存放表中行记录数据,称为数据叶,数据页以双向链表进行连接,数据页逻辑有序。
3.如果没有主键,MySQL也会建立一个聚集索引,但索引列是数据库生成的。
非聚集索引(辅助索引):
1.一张表可以有多个。
2.叶子节点存放的是一个指向聚集索引的指针,通过该表的聚集索引查询到数据页,返回查询的行数据,即使用非聚集索引存在二次查询问题。
Cardinality(索引基数):
索引列中不重复记录数量的预估值。如果记录都不重复(例如唯一约束字段,那么基数就为1)
单列索引:
定义一个索引对应一个列。
联合索引:
定义一个索引对应多个列。联合索引我们要考虑最左原则,举例说明,建立一个联合索引indx_abc(a,b,c),where a=XX;where a=XX and b=XX;where a=XX and b=XX and c=XX;都是可以使用到该索引的,但是where b=XX;where c=XX;where b=XX and c=XX是不能使用到索引的。
那么总结一下我们使用索引的原则:
1.我们建表的时候一定要指定主键,而且主键最好使用连续的数值类型,不要使用UUID。
2.要考虑数据列的Cardinality(索引基数),如果过小,则想想是否必要建立索引。
3.根据业务场景查询的情况,即where后的条件,来思考如何建立索引,是建立单列索引还是联合索引。