笔记来源:MySQL数据库教程天花板,mysql安装到mysql高级,强!硬!
冗余较小
、结构合理
的数据库,设计数据库时必须遵循一定的规则。第一范式(1NF)
、第二范式(2NF)
、第三范式(3NF)
、巴斯-科德范式(BCNF)
、第四范式(4NF)
和第五范式(5NF,又称完美范式)
。范式设计越高阶,冗余度就越低
,同时高阶的范式一定符合低阶范式的要求
,满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以次类推。一般来说,在关系型数据库设计中,最高也就遵循到BCNF
,普遍还是3NF。但也不绝对,有时候为了提高某些查询性能,我们还需要破坏范式规则,也就是反范式化
。超键
:能唯一标识元组的属性集叫做超键。候选键
:最小的超键,可能有多个主键
:用户可以从候选键中选择一个作为主键。外键
:如果数据表R1中的某属性集不是R1的主键,而是另一个数据表R2的主键,那么这个属性集就是数据表R1的外键。主属性
:包含在任一候选键中的属性称为主属性。非主属性
:与主属性相对,指的是不包含在任何一个候选键中的属性。原子性
,也就是说数据表中每个字段的值
为不可拆分
的最小数据单元。事实上,任何的DBMS都会满足第一范式的要求,不会将字段进行拆分
。举例1:
假设一家公司要存储员工的姓名和联系方式。它创建一个如下表:
该表不符合1NF,因为规则说“表的每个属性必须具有原子(单个)值”,lisi和zhaoliu员工的emp_mobile值违反了该规则。为了使表符合1NF,我们应该修改为以下表数据:
举例2:
数据表里的每一条数据记录,都是可唯一标识的
。而且所有非主键字段,都必须完全依赖主键,不能只依赖主键的一部分
。知道主键的所有属性的值,就可以检索到任何元组(行)的任何属性的任何值
。举例1:
成绩表(学号,课程号,成绩)关系中,(学号,课程号)可以决定成绩,但是学号不能决定成绩,课程号也不能决定成绩,所以“(学号,课程号)→成绩”就是完全依赖关系。
举例2:
比赛表 player_game,里面包含球员编号、姓名、年龄、比赛编号、比赛时间和比赛场地等属性,这里候选键和主键都为(球员编号,比赛编号),我们可以通过候选键(或主键)来决定如下的关系:
(球员编号,比赛编号)→(姓名,年龄,比赛时间,比赛场地,得分)
但是这个表不满足第二范式,因为数据表的字段还存在着如下的对应关系:
(球员编号)→(姓名,年龄)
(比赛编号)→(比赛时间,比赛场地)
对于非主属性来说,并非完全依赖候选键。这样会产生怎样的问题呢?
数据冗余
:如果一个球员可以参加m场比赛,那么球员的姓名和年龄就重复了m-1次。一个比赛也可能会有n个球员参加,比赛的时间和地点就重复了n-1次。插入异常
:如果我们想要添加一场新的比赛,但是这时还没有确定参加的球员都有谁,那么就没法插入。删除异常
:如果我要删除某个球员编号,如果没有单独保存比赛表的话,就会同时把比赛信息删除掉。更新异常
:如果我们调整了某个比赛的时间,那么数据表中所有这个比赛的时间都需要进行调整,否则就会出现一场比赛时间不同的情况。为了避免出现上述的情况,我们可以把球员比赛表设计为下面的三张表:
第二范式(2NF)要求实体的属性完全依赖主关键字。如果存在不完全依赖,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。
一张表只表达一个意思
,体现了单一职责原则
。
数据表中的所有非主键字段不能依赖于其他非主键字段
。通俗地讲,该规则的意思是所有非主键属性之间不能有依赖关系,必须相互独立。举例1:
符合3NF后的数据模型通俗地讲,2NF和3NF通常以这句话概括:“
每个非键属性依赖于键,依赖于整个键,并且除了键别无他物
”。
复合主键
的情况下,尤为注意
,非主键部分不应该依赖于部分主键。范式的优点
:数据的标准化有助于消除数据库中的数据冗余,第三范式(3NF)通常被认为在性能、扩展性和数据完整性方面达到了最好的平衡。范式的缺点
:范式的使用,可能降低查询的效率。因为范式等级越高,设计出来的数据表就越多、越精细,数据的冗余度就越低,进行数据查询的时候就可能需要关联多张表,这不但代价昂贵,也可能使一些索引策略无效。未必一定要符合这些标准
。开发中,我们会出现为了性能和读取效率违反范式化的原则
,通过增加少量的冗余或重复的数据来提高数据库的读性能,减少关联查询,join 表的次数,实现空间换取时间
的目的。因此在实际的设计过程中要理论结合实际,灵活运用。业务优先的原则
,首先满足业务需求,再尽量减少冗余。|反范式化
也是一种优化思路。此时,可以通过在数据表中增加冗余字段来提高数据库的读性能。冗余信息有价值或者能大幅度提高查询效率
的时候,我们才会采取反范式的优化。规范化 vs 性能
- 为满足某种商业目标,数据库性能比规范化数据库更重要
- 在数据规范化的同时,要综合考虑数据库的性能
- 通过在给定的表中添加额外的字段,以大量减少需要从中搜索信息所需的时间
- 通过在给定的表中插入计算列,以方便查询
举例1:
员工的信息存储在employees表中,部门信息存储在departments表`中。通过employees 表中的department_id字段与departments.表建立关联关系。如果要查询一个员工所在部门的名称:
select employee_id ,department_name
from employees e join departments d
on e.department_id = d.department_id;
如果经常需要进行这个操作,连接查询就会浪费很多时间。可以在employees表中增加一个冗余字段department_name,这样就不用每次都进行连接操作了。
举例2:
我们有2
个表,分别是商品流水表(trans)和商品信息表(goodsinfo)。商品流水表里有400万条流水记录,商品信息表里有2000条商品记录。
商品流水表:
商品信息表:
两个表是符合第三范式要求的。但是,在我们项目的实施过程中,对流水的查询频率很高,而且为了获取商品名称,基本都会用到与商品信息表的连接查询。
为了减少连接,我们可以直接把商品名称字段加到流水表里面。这样一来,我们就可以直接从流水表中获取商品名称字段了。虽然增加了冗余字段
,但是避免了关联查询
,提升了查询的效率
。
新的商品流水表如下所示:
巴斯-科德范式
(Boyce-Codd NormalForm)。BCNF被认为没有新的设计规范加入,只是对第三范式中设计规范要求更强,使得数据库冗余度更小。所以,称为是修正的第三范式
,或扩充的第三范式
,BCNF不被称为第四范式。只有一个候选键
,或者它的每个候选键都是单属性
,则该关系自然达到BC范式。举例1:
我们分析如下表的范式情况:
在这个表中,仓库名决定了管理员,管理员也决定了仓库名,那么(仓库名,物品名)或(管理员,无品名)都可以决定数量这个属性。这样,我们就可以找到数据表的2个候选键。
范式分析:
存在的问题:
问题解决:
消除了主属性对候选键的部分依赖或者传递依赖关系
。消除非平凡且非函数依赖的多值依赖
(即把同一表内的多对多
关系删除)。举例1:
消除不是由候选键所蕴含的连接依赖
。如果关系模式R中的每一个连接依赖均由R的候选键所隐含,则称此关系模式符合第五范式。处理的是无损连接问题
,这个范式基本没有实际意义
,因为无损连接很少出现
,而且难以察觉
。而域键范式试图定义一个终极范式,该范式考虑所有的依赖和约束类型,但是实用价值也是最小的,只存在理论研究中
。商超进货系统中的进货单表进行剖析:
这个表中的字段很多,表里的数据量也很惊人。大量重复导致表变得庞大,效率极低。如何改造?
在实际工作场景中,这种由于数据表结构设计不合理,而导致的数据重复的现象并不少见。往往是系统虽然能够运行,承载能力却很差,稍微有点流量,就会出现内存不足、CPU使用率飙升的情况,甚至会导致整个项目失败。
第一范式要求:所有的字段都是基本数据字段,不可进一步拆分。这里需要确认,所有的列中,每个字段只包含一种数据。
这张表里,我们把“property"这一字段,拆分成"specification (规格)“和“unit(单位)”,这2个字段如下:
第二范式要求,在满足第一范式的基础上,还要满足数据表里的每一条数据记录,都是可唯一标识的
。而且所有字段,都必须完全依赖主键,不能只依赖主键的一部分
。
首先,进货单明细表里面的“goodsname(名称)"“specification(规格)”"unit(单位)"这些信息是商品的属性,只依赖于“barcode(条码)”,不完全依赖主键,可以拆分出去。我们把这3个字段加上它们所依赖的字段“"barcode(条码)”,拆分形成一个新的数据表“商品信息表”。
这样一来,原来的数据表就被拆分成了两个表:
此外,字段“supplierid(供应商编号)"“suppliername(供应商名称)”“stock(仓库)“只依赖于“listnumber(单号)”,不完全依赖于主键,所以,我们可以把"supplierid”“suppliername”“stock"这3个字段拆出去,再加上它们依赖的字段”"listnumber(单号)”,就形成了一个新的表”进货单头表”。剩下的字段,会组成新的表,我们叫它"进货单明细表”。原来的数据表就拆分成了3个表:
第3步,在“商品信息表"中,字段“barcode"是有可能存在重复的,比如,用户门店可能有散装称重商品和自产商品,会存在条码共用的情况。所以,所有的字段都不能唯一标识表里的记录。这个时候,我们必须给这个表加上一个主键,比如说是自增字段“itemnumber"。
我们的进货单头表,还有数据冗余的可能。因为“suppliername”依赖“supplierid”,那么,这个时候,就可以按照第三范式的原则进行拆分了。我们就进一步拆分下进货单头表,把它解析成供货商表和进货单头表:
客观存在的事物
、事物的属性
,以及事物之间关系
的一种数据模型。在开发基于数据库的信息系统的设计阶段,通常使用ER模型来描述信息需求和信息特性,帮助我们理清业务逻辑,从而设计出优秀的数据库。实体
:可以看做是教据对象,往往对应于现实生活中的真实存在的个体。在ER模型中,用矩形来表示。实体分为两类,分别是强实体和弱实体。
属性
:则是指实体的特性。比如超市的地址、联系电话、员工数等。在ER模型中用椭圆形来表示。关系
:则是指实体之间的联系。比如超市把商品卖给顾客,就是一种超市与顾客之间的联系。在ER模型中用菱形来表示。一对一
:指实体之间的关系是一一对应的,比如个人与身份证信息之间的关系就是一对一的关系。一个人只能有一个身份证信息,一个身份证信息也只属于一个人。一对多
∶指一边的实体通过关系,可以对应多个另外一边的实体。相反,另外一边的实体通过这个关系,则只能对应唯一的一边的实体。比如说,我们新建一个班级表,而每个班级都有多个学生,每个学生则对应一个班级,班级对学生就是一对多的关系。多对多
︰指关系两边的实体都可以通过关系对应多个对方的实体。比如在进货模块中,供货商与超市之间的关系就是多对多的关系,一个供货商可以给多个超市供货,一个超市也可以从多个供货商那里采购商品。再比如一个选课表,有许多科目,每个科目有很多学生选,而每个学生又可以选择多个科目,这就是多对多的关系。ER模型看起来比较麻烦,但是对我们把控项目整体非常重要。如果你只是开发一个小应用,或许简单设计几个表够用了,一旦要设计有一定规模的应用,在项目的初始阶段,建立完整的ER模型就非常关键了。开发应用项目的实质,其实就是建模。
我们设计的案例是电商业务,由于电商业务太过庞大且复杂,所以我们做了业务简化,电商业务设计总共有8个实体,如下所示:
其中,用户和商品分类是强实体,因为它们不需要依赖其他任何实体。而其他属于弱实体,因为它们虽然都可以独立存在,但是它们都依赖用户这个实体,因此都是弱实体。知道了这些要素,我们就可以给电商业务创建ER模型了,如图:
在这个图中,地址和用户之间的添加关系,是一对多的关系,而商品和商品详情示一对1的关系,商品和订单是多对多的关系。这个ER模型,包括了8个实体之间的8种关系。
有了这个ER模型,我们就可以从整体上理解电商的业务了。刚刚的ER模型展示了电商业务的框架,但是只包括了订单,地址,用户,购物车,评论,商品,商品分类和订单详情这八个实体,以及它们之间的关系,还不能对应到具体的表,以及表与表之间的关联。我们需要把属性加上,用椭圆来表示,这样我们得到的ER模型就更加完整了。
外键约束
主要是在数据库层面上保证数据的一致性
,但是因为插入和更新数据需要检查外键,理论上性能会有所下降
,对性能是负面的影响。实际的项目
,不建议使用外键
,一方面是降低开发的复杂度(有外键的话主从表类的操作必须先操作主表),另外是有外键在处理数据的时候非常麻烦。在电商平台,由于并发业务量比较大,所以一般不设置外键,以免影响数据库性能。在应用层面做数据的一致性检查
,本来就是一个正常的功能需求。如学生选课的场景,课程肯定不是输入的而是通过下拉或查找等方式从系统中进行选取,就能够保证是合法的课程ID,因此就不需要靠数据库的外键来检查了
。三少一多
”:
数据表的个数越少越好
:RDBMS的核心在于对实体和联系的定义,也就是E-R图(Entity Relationship Diagram),数据表越少
,证明实体和联系设计得越简洁
,既方便理解又方便操作。数据表中的字段个数越少越好
:字段个数越多,数据冗余的可能性越大。设置字段个数少的前提是各个字段相互独立,而不是某个字段的取值可以由其他字段计算出来。当然字段个数少是相对的,我们通常会在数据冗余和检索效率中进行平衡
。数据表中联合主键的字段个数越少越好
:设置主键是为了确定唯一性,当一个字段无法确定唯一性的时候,就需要采用联合主键的方式(也就是用多个字段来定义一个主键)。联合主键中的字段越多,占用的索引空间越大
,不仅会加大理解难度,还会增加运行时间和索引空间,因此联合主键的字段个数越少越好。使用主键和外键越多越好
:数据库的设计实际上就是定义各种表,以及各种字段之间的关系。这些关系越多,证明这些实体之间的冗余度越低,利用度越高。这样做的好处在于不仅保证了数据表之间的独立性,还能提升相互之间的关联使用率。简单可复用
。简单指的是用更少的表、更少的字段、更少的联合主键字段来完成数据表的设计。可复用则是通过主键、外键的使用来增强数据表之间的复用率。因为一个主键可以理解是一张表的代表。键设计得越多,证明它们之间的利用率越高。32个
字符以内,只能使用英文字母
、数字
和下划线
,建议以英文字母开头
。小写
,不同单词采用下划线分割。须见名知意
。禁止使用关键字
(type,order等)。显式指定字符集
,并且字符集只能是utf8或者utf8mb4。权限最小原则
,使用数据库账号只能在一个DB下使用,不准跨库。程序使用的账号原则上不准有drop权限。tmp_
为前缀,并以日期为后缀; 备份库以bak_
为前缀,并以日期为后缀。32
个字符以内,表名只能使用英文字母
、数字
和下划线
,建议以英文字母开头
。小写
,不同单词采用下划线分割。须见名知意
。显式指定字符集
为utf8或utf8mb4。禁止使用关键字
(如type,order等)。显式指定表存储引擎类型
。如无特殊需求,一律为InnoDB。建表必须有comment
。尽可能使用表达实际含义的英文单词或缩写
。如:公司ID,不要使用corporation_id,而用corp_id即可。is_描述
。如member表上表示是否为enabled的会员的字段命名为is_enabled。禁止在数据库中存储图片
、文件等大的二进制数据,数据库只存储文件地址信息
。创建时间
字段(create_time)和最后更新时间
字段(update_time),便于查问题。可视化工具
。这样可以确保表、字段相关的约定都能设置上。id
int/bigint auto_increment,且主键值禁止被更新
。索引类型必须为BTREE
。几个单词首字母
,加末单词组成column_name。如: sample表member_id上的索引: idx_sample_mid。6
个。多考虑建立联合索引
,并把区分度最高的字段放在最前面
。保证被驱动表的连接列上有索引
,这样JOIN执行效率最高。不存在冗余索引
。比如:如果表里已经存在key(a,b),则key(a)为冗余索引,需要删除。禁止写成*
DML语句必须有WHERE条件
,且使用索引查找。SELECT语句不要使用UNION
,推荐使用UNION ALL
,并且UNION子句个数限制在5
个以内。5
个表。减少使用ORDER BY
,和业务沟通能不排序就不排序,或将排序放到程序端去做
。