索引的定义是:系统根据某种算法,将已有的数据(未来可能新增的数据)单独建立一个文件,这个文件能够实现快速的匹配数据,并且能够快速的找到对应的表中的记录。
索引的意义:
1. 提升查询数据的效率
2. 约束数据的有效性(唯一性等)
增加索引是有前提条件的:索引本身会产生索引文件(有时候有可能比数据文件还大),会非常耗费磁盘空间。如果某个字段需要作为查询的条件经常使用,那么使用索引。或者如果某个字段需要进行数据的有效性约束,也可能使用索引。
Mysql提供了多种索引。
1. 主键索引:primary key
2. 唯一索引:unique key
3. 全文索引:fulltext index
4. 普通索引:index
全文索引:针对文章内部的关键字进行索引,全文索引最大的问题在于如何确定关键字。英文关键字很容易,英文单词与单词之间有空格。但是中午很难,中文没有空格,而且中文可以各种随意组合,中文的分词(sphinx)最难。索引涉及到SQL优化的问题,很复杂的课题,这里先放放。
关系就是实体与实体之间的关系,我们将这种关系分为三种:一对一,一对多,多对多。一般我们指的关系是表与表之间关系。
一对一
一张表的一条记录一定只能与另外一张表的一条记录进行对应,反之亦然。
下面用学生表来举例。
学生表:姓名,性别,年龄,身高,体重,婚姻状况,籍贯,家庭住址,紧急联系人。
Id(P) |
姓名 |
性别 |
年龄 |
身高 |
体重 |
婚姻状况 |
籍贯 |
家庭住址 |
紧急联系人 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
表设计成以上这种形式,不是不可以,它确实符合要求。但是其中性别,年龄,体重生属于常用数据,但是婚姻状况,籍贯,住址,联系人属于不常用数据。如果每次查询都是查询所有数据,那么这些不常用数据会影响查询效率,实际这些不常用数据又不用。这种情况,解决方案就是,将常用数据和不常用数据分离,分离成两张表。
常用信息表
Id(P) |
姓名 |
性别 |
年龄 |
身高 |
体重 |
|
|
|
|
|
|
|
|
|
|
|
|
不常用信息表
婚姻状况 |
籍贯 |
家庭住址 |
紧急联系人 |
|
|
|
|
|
|
|
|
如果两张表分成这样,那么问题就来,如果我们需要查询某个人的家庭住址,那么怎么查询呢?也就是两个表之间没有关联关系。解决办法就是,让不常用信息和常用信息一定能够对应得上,找一个具有唯一性的字段来共同连接两张表。通过分析和观察,我们可以拿ID这个字段来将两张表共同连接起来。
所以不常用信息表可以改成这样。
Id(P) |
婚姻状况 |
籍贯 |
家庭住址 |
紧急联系人 |
|
|
|
|
|
|
|
|
|
|
因为id是主键,是唯一的(上面表中P表示主键,以下也是P代表主键),这样两张表就有了关系,在常用表中的一条记录,永远只能在不常用表中匹配一条记录,反过来,在不常用表中一条记录,永远只能在常用表中匹配一条记录,这个关系就是一对一。
一对多(多对一)
定义:一张表中有一条记录可以对应另外一张表中的多条记录,但是反过来,另外一张表的一条记录只能对应第一张表的一条记录,这种关系就是一对多,或者多对一。
下面通过一个国家有多个省份来举例一对多的关系
国家表
ID(P) |
名称 |
全球所属位置 |
|
|
|
省份表
ID(P) |
名称 |
所在国家位置 |
|
|
|
以上关系,一个国家可以在省份表中找到多条记录(也有可能是一条),但是一个省份表永远只能找到一个国家。这就是典型的一对多关系。但是以上设计,解决了实体的设计表问题,但是没有解决关系问题,省份找不出国家,国家找不到省份。
解决方案:在某一张表中增加一个字,能够找到另外一张表的中的记录。应该在省份表中增加一个字段指向国家表,因为省份表的记录只能匹配到一条国家记录。
修改后的省份表如下
ID(P) |
名称 |
所在国家位置 |
国家ID(P) |
|
|
|
|
多对多
定义:一张表(A)中的一条记录能够对应另外一张表(B)中的多条记录,同时B表中的一条记录也能在对应A表中的多条记录,这就是多对多关心。
以下通过教师教学,老师和学生表来设计举例。
老师表
T_Id(P) |
姓名 |
性别 |
|
|
|
学生表
S_Id(P) |
姓名 |
性别 |
|
|
|
以上设计方案实现了实体的设计,但是没有维护实体之间的关系。一个老师教过多个学生,一个学生也被多个老师教过,这种场景以上表就体现不了。
解决方案:不管在哪张表增加字段,都会出现一个问题,该字段要保存多个数据,而且是与该表有关系的字段,不符合表设计规范,这种情况,我们增加一个中间关系表。
具体设计如下:
老师表
T_Id(P) |
姓名 |
性别 |
|
|
|
学生表
S_Id(P) |
姓名 |
性别 |
|
|
|
中间关系表:
ID |
T_Id(老师) |
S_ID(学生) |
1 |
|
|
2 |
|
|
增加中间表之后,中间表与老师表形成一对多关系,而且中间关系表是多表。这样就能够唯一找到一表的关系。同样的学生表与中间表也是一对多的关系。
学生找老师的过程:1 找出学生id-> 2中间表寻找匹配记录(多条)-> 3老师表匹配(一条)
老师找学生过程: 1 找出学生id –> 2中间表寻找匹配记录(多条)-> 3学生表匹配(一条)
这样就从一对多,然后到多对一,从而就得到结果就是多对多的效果。这种多对多,在现实项目中最常见的就是电子商务网站上的订单和会员之间的关系。
范式(Normal Format),是一种离散数学中的知识,是为了解决一种数据的存储与优化的问题,保存数据的存储之后,凡是能够通过关系寻找出来的数据,坚决不再重复存储终极目标是为了减少数据的冗余。范式是一种分层结构的规范,分为六层:每一次层都比上一层更加严格,若要满足下一层范式,前提是满足上一层范式。
六层范式:1NF, 2NF, 3NF, 4NF, 5NF, 6NF, 1NF是最底层,要求最低,6NF是最高层,最严格。
Mysql属于关系型数据库,有空间浪费,也是需要考虑节省存储空间,这个与范式所有解决的问题不谋而合。在设计数据库的时候,会利用范式来指导设计。但是数据库不单是要解决空间问题,还要保证效率问题。范式只为解决空间问题,所以数据库的设计又不可能完全按照范式的要求去设计和实现,一般情况下,只有前三种范式需要满足。范式只是指导意义,没有强制规范要求。
第一范式
定义:在设计表存储数据的时候,如果表中设计的字段存储的数据,在取出来使用之前还需要额外的处理(拆分),那么说表的设计不满足第一范式,第一范式要求字段的数据具有原子性,也就是不可拆分。
举例下面讲师表
讲师 |
性别 |
班级 |
课程 |
时间 |
代课时间(开始和结束) |
张三 |
男 |
204 |
Java基础 |
30天 |
2018-01-01,2018-01-31 |
|
|
|
|
|
|
看上面这个表格,其实是可以存储数据,但是不符合第一范式。理由是这样的,如果要查询一个老师是从什么时间开始代课和什么时间结束代课。这样查询结果应该是两个字段,但是当前表只能给出一个字段,也就是查询之后还需要拆分数据。
解决方案,把代课时间分拆两个字段,一个开始一个结束。
讲师 |
性别 |
班级 |
课程 |
时间 |
开始 |
结束 |
张三 |
男 |
204 |
Java基础 |
30天 |
2018-01-01 |
2018-01-31 |
|
|
|
|
|
|
|
第二范式
定义:在数据表设计的过程中,如果有复合主键(多字段主键),且表中有字段并不是由整个主键来确定,而是依赖主键中的某个字段(主键部分),存在字段依赖主键部分的问题称之为部分依赖,第二范式就是解决表数据中不允许出现部分依赖。
举例:讲师代课表
讲师(P) |
性别 |
教室 |
班级(P) |
时间 |
开始 |
结束 |
张三 |
男 |
204 |
Java基础 |
30天 |
2018-01-01 |
2018-01-31 |
|
|
|
|
|
|
|
以上表中有两个P。表示讲师和班级两个字段组成复合主键约束。一个老师在一个班永远只带一个阶段的课。代课时间,开始和结束时间都与当前的代课主键有关系,但是性别并不依赖班级,教室不依赖讲师,性别只依赖讲师,教室只依赖班级。这个就形成了部分依赖,符合第二范式。
解决方案1:可以将性别和讲师单独成表,班级和教室单独成表。
解决方案2:取消复合主键,使用逻辑主键。
我们来介绍方案2
ID(P) |
讲师 |
性别 |
教室 |
班级 |
时间 |
开始 |
结束 |
1 |
张三 |
男 |
204 |
Java基础 |
30天 |
2018-01-01 |
2018-01-31 |
2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
当前增加了ID这个逻辑主键,取消讲师和班级复合主键。
第三范式
要满足第三范式,必须满足第二范式。理论上讲,应该一张表中的所有字段都应该直接依赖主键(逻辑主键,代表的是业务主键),如果表设计中存在一个字段并不直接依赖主键,而是通过某个非主键字段依赖,最终实现依赖主键,把这种不是直接依赖主键,而是依赖非主键字段的依赖关系称之为传递依赖。
举例:讲师代课表
ID(P) |
讲师 |
性别 |
教室 |
班级 |
时间 |
开始 |
结束 |
1 |
张三 |
男 |
204 |
Java基础 |
30天 |
2018-01-01 |
2018-01-31 |
2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
以上表设计方案中,性别依赖讲师存在,讲师依赖主键;教室依赖班级,而班级依赖主键。性别和教室都存在传递依赖。
解决方案:将存在传递依赖的字段以及依赖字段本身单独取出,形成单独表,然后在需要对应的信息的时候,使用对应实体表的主键加进来。
讲师代课表
ID(P) |
讲师ID |
班级ID |
时间 |
开始 |
结束 |
1 |
1 |
10 |
30天 |
2018-01-01 |
2018-01-31 |
2 |
2 |
20 |
|
|
|
|
|
|
|
|
|
讲师表
ID |
讲师 |
性别 |
1 |
张三 |
男 |
班级表
ID |
班级 |
教室 |
10 |
Java基础 |
201 |
讲师表中ID=讲师,同样班级表中ID=班级,因为讲师可能存在名称重名的,所以增加ID这个逻辑主键。
范式逆规范化
有时候在设计表的时候,如果一张表中有几个字段是需要从另外的表中去获取信息,理论上讲,的确可以获取到想要的数据,但是就是查询效率低一点,会可以的在某些表中,不去保存另外表的主键(逻辑主键),而是直接保存想要的数据信息,这样一来,在查询数据的时候,一张表可以直接提供数据,而不需要多表查询,但是会导致数据冗余增加,这个方式就是范式逆规范化。
举例:还是讲师代课表
ID(P) |
讲师ID |
班级ID |
时间 |
开始 |
结束 |
1 |
张三 |
Java基础 |
30天 |
2018-01-01 |
2018-01-31 |
2 |
李四 |
Python基础 |
|
|
|
|
|
|
|
|
|
上面我们不存储讲师和班级ID,直接存储讲师名称和班级名称,这样我们就在一张表就可以获取查询讲师和班级信息。
所谓逆规范化就是磁盘的利用率和查询效率的博弈。总结,范式只需要了解1NF,2NF,3NF,可以锁第四范式以后限制条件越多,效率反而越低。