文章目录
数据库建模就是数据库结构设计
业务分析(人和):一切故事的开始
我们这里以慕课网的免费前端项目进行分析
- 课程的属性:{主标题,副标题,方向,分类,难度,最新,最热,时长,简介,人数,需知,收获,讲师名,讲师职位,课程图片,综合评分,内容实用,简洁易懂,逻辑清晰}
- 课程列表的属性:{章名,小节名,说明,小节时长,章节URL,视频格式}
- 讲师属性:{讲师昵称,说明,性别,省,市,职位,说明,经验,积分,关注人数,粉丝人数}
- 问答评论属性:{类型,标题,内容,关联章节,浏览量,发布时间,用户昵称}
- 笔记的属性:{用户昵称,关联章节,笔记标题,笔记内容,发布时间}
- 用户的属性:{用户昵称,密码,说明,性别,省,市,职位,说明,经验,积分,关注人数,粉丝人数}
- 评价属性:{用户,课程主标题,内容,综合评分,内容实用,简洁易懂,逻辑清晰,发布时间}
充分理解了业务需求,归纳出业务需求中所列的这些对象和属性之后,就可以开始数据库的逻辑设计。对于一个对象,我们想到以什么方式进行存储呢?
首先想到的是以宽表模式进行存储
宽表模式
把一个对象的所有属性全存储在一个表中。如果这个对象属性很多,表的列就会比较多,这样就比较宽大,以课程的属性为例:
- 课程的属性:{主标题,副标题,方向,分类,难度,最新,最热,时长,简介,人数,需知,收获,讲师名,讲师职位,课程图片,综合评分,内容实用,简洁易懂,逻辑清晰}
创建一个课程宽表,将会有19列,这样会存在什么问题呢?
主标题 | 副标题 | 方向 | 分类 | 难度 | 讲师名 | 讲师职位 | 综合评分 |
---|---|---|---|---|---|---|---|
MySQL面试指南 | 中高级IT开发人员晋升加薪的必备佳品! | 数据库 | Mysql | 中级 | sqlsern | 高级DBA | 10 |
MyCAT+MySQL | MyCAT高可用数据库架构 | 数据库 | Mysql | 中级 | sqlsern | 高级DBA | 10 |
MySQL架构设计 | 高性能MySQL架构设计与优化 | 数据库 | Mysql | 中级 | sqlsern | 高级DBA | 9.15 |
扛得住的MySQL | MySQL提升课程全面讲解MySQL架构设计 | 数据库 | Mysql | 中级 | sqlsern | 高级DBA | 9.94 |
宽表模式存在的问题
- 数据冗余
每一行都存在重复数据,比如“讲师名”、“分类”等,如果一个讲师录制了很多门课程,这个相同的信息就会出现多次。 - 数据更新异常:修改一行中某列的值时,同时修改了多行数据
例如,我们修改讲师职位时候,受影响的并不止有一行,而是把所有讲师为’sqlercn‘讲师的所有信息都更新了
UPDATE 课程表 SET 讲师职位='MySQL架构师'
WHERE 讲师名=‘sqlercn'
也许你说是写的SQL有问题,如果SQL中加个条件来限定只更新主标题为”MySQL面指南“这门课程的讲师职位信息,那是不是就可以了呢?
UPDATE 课程表 SET 讲师职位='MySQL架构师'
WHERE 讲师名=‘sqlercn' AND 主标题=’MySQL面指南‘
这样虽然可以更新,但是这将导致表中数据不一致,讲师为’sqlercn‘的课程记录的’讲师职位‘有的是’高级DBA‘,而有些是’MySQL架构师‘
- 数据插入异常:部分数据由于缺失主键信息而无法写入表中
我们需要向课程表中增加’课程方向‘为’后端开发‘的记录,并且现在还没有后端开发方向的课程,只是为了在前端分类中展示该课程方向,执行下面SQL:
INSESRT INTO 课程表(方向)VALUES(’后端开发‘)
上面语句由于没有主键’主标题‘,而导致插入失败,无法在课程表中维护,也就无法在前端进行展示,这显然是不合理的
- 数据的删除异常:删除某一数据时不得不删除另一数据
比如我们删除’方向‘为’数据库‘的课程,
DELETE FROM 课程表 WHERE 方向= ’数据库‘
宽表模式的应用场景
- 配合列存储的数据报表应用
由于宽表的所有数据都在一个表中,查询的时候不需要再关联多个表,所以SQL执行效率相对比较高,并且宽表模式存在的诸如插入异常、更新异常、删除异常等在报表应用中,往往不是什么太大的问题,所以当前设计一个报表结构的话,使用宽表模式是完全没有问题的
逻辑设计(天时):范式化 VS 反范式化
数据库设计是有一定规则参考的,这个规则就是数据库的设计范式
数据库设计范式
数据库设计范式有很多,不过通常来说呀,我们设计的数据库只要符合前三个范式的要求就已经足够了,接下来看下这三个范式是什么
- 第一范式:表中的所有字段都是不可再分的
什么意思呢?我们看个例子:
联系方式又分为了手机、固话、邮箱三个属性,也就是说联系方式是个复合属性,这就违反了第一范式的要求,并且这样的表是无法在数据库中建立的。我们要怎么修改用户表的设计,才能让其满足第一范式的要求呢?
其实很简单,我们只要将’联系方式‘这一列进行拆分就可以了
姓名 | 性别 | 手机 | 固话 | 邮箱 |
---|---|---|---|---|
张三 | 男 | 13801380001 | 87600000 | zhangsan@qq.com |
- 第二范式:表中必须存在业务主键,并且非主键依赖于全部业务主键
第二范式是在第一范式基础上成立的,也就是说第二范式的表一定是一个标准的二维表,并且在二维表中,要有一个业务主键,而且表中其它列也要求全部依赖业务主键。
大家注意了,这里有两个要点,一个是’业务主键‘,什么是业务主键呢?业务主键就是那些可以唯一标识出每一行数据的列或列的组合,比如上面的课程表的主标题 列就是业务主键。如果我们使用的是组合列做业务主键,还要求组合列以外其它列也都依赖组合列,而不是只依赖组合列中的一个或部分列的情况,比如笔记表:
用户 | 章节 | 标题 | 内容 | 用户积分 | 时间 |
---|---|---|---|---|---|
张三 | 1-1 | 笔记-1 | 测试笔记 | 300 | 2018-12-24 |
如果我们使用’用户‘列来做表的主键是否可行呢?一个用户可以有多条笔记,所以用户无法为一个业务主键,同样的一个章节也可以有多个不同的用户来使用 ,笔记的标题也可能会有重复,所以在这个表中无法使用单一的某一列作为主键。
如果使用’用户’‘章节’‘标题’来做组合主键,那我们再来看其它的列同符合主键全部存在依赖关系呢?首先,时间列是没有问题的,问题出现在用户积分列上,可以发现用户积分只同用户列存在依赖关系的,所以这个表目前就不符合第二范式的要求。
我们怎么修改,才能符合第二范式呢?其实也是很简单的,主要把不存在依赖关系的列全部提出来单独放到一个表中,是不是就可以了呢,这样就变成笔记表、用户表两个表了
用户 | 章节 | 标题 | 内容 | 时间 |
---|---|---|---|---|
张三 | 1-1 | 笔记-1 | 测试笔记 | 2018-12-24 |
用户 | 用户积分 | … |
---|---|---|
张三 | 300 | … |
从上面例子,有没有发现——如果一个表是用一个列做业务主键的,那么这个表天生就符合第二范式的;如果这个表有多个列做为主键,那就要区分对待看它是否符合第二范式
- 第三范式:表中的非主键列之间不能相互依赖
第三范式是在第二范式基础上要求的,要求非主键列除了对主键列有依赖关系外,不能依赖表中的其它列。我们通过一个例子来看下
主标题 (PK) | 副标题 | 方向 | 分类 | 难度 | 讲师名 | 讲师职位 | 综合评分 |
---|---|---|---|---|---|---|---|
MySQL面试指南 | 中高级IT开发人员晋升加薪的必备佳品! | 数据库 | Mysql | 中级 | sqlsern | 高级DBA | 10 |
MyCAT+MySQL | MyCAT高可用数据库架构 | 数据库 | Mysql | 中级 | sqlsern | 高级DBA | 10 |
MySQL架构设计 | 高性能MySQL架构设计与优化 | 数据库 | Mysql | 中级 | sqlsern | 高级DBA | 9.15 |
扛得住的MySQL | MySQL提升课程全面讲解MySQL架构设计 | 数据库 | Mysql | 中级 | sqlsern | 高级DBA | 9.94 |
上面表,在说宽表模式时候使用过。这张表使用课程的主标题来做为主键,毕竟不可能存在两个名称同名的课程,这一点看它是符合第二范式的,不过也不能避免一些问题,我们继续来看课程表是否满足第三范式呢?
副标题 、 方向 、 分类 、 难度 、综合评分 列只跟主标题存在依赖关系, 讲师名 列也跟主标题存在依赖关系,而 讲师职位 呢其实只跟讲师名存在依赖关系,和课程主标题列就无直接依赖关系,所以我们说该表不符合第三范式要求,那我们怎么修改这个表,让它符合第三范式要求呢?
我们只要将不符合直接依赖关系的列,把它提出来放到一个单独的表中就可以了,这样就得到一个不包括讲师职位的课程表和一个讲师表:
主标题 (PK) | 副标题 | 方向 | 分类 | 难度 | 讲师名 | 综合评分 |
---|---|---|---|---|---|---|
MySQL面试指南 | 副标题 | 数据库 | Mysql | 中级 | sqlsern | 10 |
MyCAT+MySQL | 副标题 | 数据库 | Mysql | 中级 | sqlsern | 10 |
MySQL架构设计 | 副标题 | 数据库 | Mysql | 中级 | sqlsern | 9.15 |
扛得住的MySQL | 副标题 | 数据库 | Mysql | 中级 | sqlsern | 9.94 |
讲师名(PK) | 讲师职位 |
---|---|
sqlsern | 高级DBA |
sqlsern | 高级DBA |
sqlsern | 高级DBA |
sqlsern | 高级DBA |
数据库的范式还有很多,比如第四、第五、第六范式,不过在我们设计过程中,只要满足第三范式的要求就可以了
根据设计范式对业务对象逻辑建表
课程对象
- 课程的属性:{主标题,副标题,方向,分类,难度,最新,最热,时长,简介,人数,需知,收获,讲师昵称,讲师职位,课程图片,综合评分,内容实用,简洁易懂,逻辑清晰}
课程的属性跟第三范式,我们需要如何来存储呢?我们首先可以将课程对象拆分成课程表
主标题 (PK) | 副标题 | 方向 | 分类 | 难度 | 上线时间 | 学习人数 | 时长 | 简介 | 需知 | 收获 | 讲师昵称 | 课程图片 | 综合评分 | 内容实用 | 简洁易懂 | 逻辑清晰 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
课程表可以使用主标题做为业务主键,那么接下来看看那些属性是和主标题存在直接依赖关系的呢?
课程的副标题、方向、分类、难度 都是直接同课程表的业务主键相依赖
课程是否是最新的这个属性,实际上我们可以通过课程的上线时间来计算得出,而业务上可以规定上线多长时间的课程可以定为最新的课程,所以这里使用上线时间属性来代替是否为最新的属性;同样,最热属性也可以根据学习人数来计算得出,只要按学习人数来排序,学习人数越高热度呢也就越高
时长、简介也是同主键主标题直接相关的,下一个人数在表中已经有了
需知、收获也和主键相关联,也可以放在表里
讲师昵称、讲师职位,我们在设计的第三范式中已经分析过了,需要提出来单独建表,建一个讲师表
讲师昵称(pk) | 讲师职位 |
---|---|
当然讲师表还有其它的一些属性,我们在分析其他的业务对象的时候再加入。另外由于课程和讲师之间存在一对一(多对一)的关系 ,我们需要将主键讲师昵称也加入到课程表中
课程图片也是和课程主键直接依赖关系的
接下来,综合评分、内容实用、简洁易懂、逻辑清晰 这些每个用户对课程的评价也和主键相关的,我们也可以放在课程表
另外,对于方向、分类、难度 虽然和课程表主键相关的,不过存储在课程表还是会出现重复,还是会出现数据的插入、更新、删除的异常,所以需要几个单独的表来存储这些属性
课程方向表:
课程方向名称(pk) | 填加时间 |
---|---|
课程分类表:
分类名称(PK) | 填加时间 |
---|---|
微服务 | 2019-05-30 12:12:12 |
区块链 | 2019-05-30 12:12:12 |
人工智能 | 2019-05-30 12:12:12 |
课程难度表:
课程难度(PK) | 填加时间 |
---|---|
入门级 | 2019-05-30 12:12:12 |
初级 | 2019-05-30 12:12:12 |
中级 | 2019-05-30 12:12:12 |
高级 | 2019-05-30 12:12:12 |
课程列表对象
- 课程列表的属性:{章节名、小节名、说明、小节时长、章节url、视频格式}
章节名(pk) | 小节名(pk) | 说明 | 小节时长 | 章节url | 视频格式 |
---|---|---|---|---|---|
很显然需要使用章节名、小节名联合做为业务主键,才能保证主键的唯一性
章节说明其实是同课程列表的章节名具有依赖关系,同时后面的小节时长、章节url、视频格式也只同小节名有依赖关系,所以如果将这些属性放在一张表中,显然不满足第二范式的表中的非主键列必须对主键列具有依赖关系的要求,所以我们不能把这些字段放在一个表中了,我们要怎么来拆分呢?
上面分析了课程列表业务主键是一个联合主键,而联合主键的每个属性都有和其相互依赖的属性,所以我们可以把这两个联合主键拆分开,把不同主键相依赖的属性单独放在一个表中,具体来说就是把课程的章节名、章节说明独立出来,形成一张章节表
课程章节表:
课程章节名(pk) | 说明 | 章节编号 |
---|---|---|
同时为了保存课程表中同课程章节表的关联关系,还需要一张章节表和课程表之间的关联表,这张表只有两个属性,就是课程表的主键和章节表的主键
课程同章节表的联系表:
课程主标题 | 课程章节名 |
---|---|
课程小节表:
小节名称(pk) | 小节视频url | 视频格式 | 小节时长 | 小节编号 |
---|---|---|---|---|
为了保存小节与章节之间的关联关系,同样需要一张课程的小节表同章节表之间的关联关系表
课程章节同小节关系表:
课程主标题 | 课程章节名 | 小节名称 |
---|---|---|
讲师对象
- 讲师的属性:{讲师昵称、密码、性别、省、市、职位、说明、经验、积分、关注人数、粉丝人数}
首先,我们找到讲师的属性中可以做为主键的属性,显然讲师昵称是可以唯一标识出讲师表中每一行记录的,很适合做为业务主键来使用,并且是一个单属性值的主键,因此天生就已经符合了第二范式的要求。
接下来,看其他属性是否跟主键间存在直接依赖关系,可以看到其它属性也和讲师昵称完全依赖的,所以讲师实体的所有属性都可以存放到一张讲师表,并且可以使用讲师昵称来做为讲师表的主键
在前面分析课程属性的时候,也拆分出了一个讲师表,并且那个讲师表中只有讲师昵称、讲师职位两个属性,我们可以看到这两个属性也已经包含在了现在的讲师表中了
讲师昵称 (pk) | 密码 | 性别 | 省 | 市 | 职位 | 说明 | 经验 | 积分 | 关注人数 | 粉丝人数 |
---|---|---|---|---|---|---|---|---|---|---|
用户对象
- 用户的属性:{用户昵称、密码、性别、省、市、职位、说明、经验、积分、关注人数、粉丝人数}
同样可以使用用户昵称来标识每一个用户,这时就要求我们用户表的每一个昵称它是不能重复的,并且可以发现,在用户表中其他的属性如用户密码、说明、性别等其他属性也都是同用户昵称相依赖的,所以可以把所有的用户属性都存储在一张表中
用户昵称 (pk) | 密码 | 性别 | 省 | 市 | 职位 | 说明 | 经验 | 积分 | 关注人数 | 粉丝人数 |
---|---|---|---|---|---|---|---|---|---|---|
现在我们来比较下用户表和讲师表,可以发现是十分类似的,那么除了主键名称分为讲师昵称和用户昵称之外,其他的属性完全相同,并且讲师也是一个用户,如果我们要使用讲师表和用户表两个表来存储用户信息的话,讲师这个用户的信息呀很可能要存储到两个表中,这样就会造成数据的冗余,并且很难保证讲师表和用户表数据信息是一致的,所以我们需要考虑将用户表和讲师表进行合并
合并后的用户表:
用户昵称 (pk) | 密码 | 性别 | 省 | 市 | 职位 | 说明 | 经验 | 积分 | 关注人数 | 粉丝人数 | 讲师标识 |
---|---|---|---|---|---|---|---|---|---|---|---|
可以看到,比用户表多了一个讲师标识,用来标识这个用户是否为讲师,这样就避免了讲师这个用户同时存在于讲师表和用户表中,造成的数据冗余问题了
问答评论对象
- 问答评论属性:{类型、标题、内容、关联章节、浏览量、发布时间、用户昵称}
上面属性中,用哪些属性来标识问答评论的一条记录是合适的呢?评论标题做为业务主键是否合适呢?我们不能保证所有用户发布的评论标题完全不相同,所以不能使用单一的标题属性来标识一条记录。并且同一用户在不同章节发布的评论也可能相同的,所以这里只能使用用户昵称、关联章节、标题三个属性来标识问答或评论
如何来记录关联章节呢?是不是只能使用课程章节的业务主键来记录呢?因此不得不把课程章节的关联表中业务主键课程主标题、课程章名、小节名称
课程主标题 | 课程章节名 | 小节名称 |
---|---|---|
这就组成了问答评论表:
标题(pk) | 课程主标题(pk) | 课程章节(pk) | 小节名称(pk) | 用户昵称(pk) | 父评论标题 | 内容 | 类型 | 浏览量 | 发布时间 |
---|---|---|---|---|---|---|---|---|---|
是不是感觉这个表的结构有些怪异呢?数据好像有一些冗余,这个先不用着急,这还不是我们最终的表结构,后面还会对这个表进行优化
另外,在评论中还增加了一个父评论标题这一属性,这个属性做什么用呢?我们知道无论是问答还是评论都是可以被回复的,如果这条评论是对其他的评论或问答回复的话,那这个父标题所记录的就是被回复的问题或评论的标题
笔记对象
- 笔记的属性:{用户昵称、关联章节、笔记标题、笔记内容、发布时间}
可以看到,笔记的属性和问答评论的属性很相似,同样包含了发布笔记的用户昵称、关联章节、笔记标题、笔记内容、发布时间这些属性,同样的我们不能使用笔记标题来做为一条笔记的唯一标识,所以还是需要使用到用户昵称、关联章节 、笔记标题这些属性共同来标识一条笔记,这里也需要对关联章节属性用课程小节关系表中的业务主键来进行转换,最终形成了下面这张表
笔记标题(pk) | 课程主标题(pk) | 课程章节(pk) | 小节名称(pk) | 用户昵称(pk) | 笔记内容 | 发布时间 |
---|---|---|---|---|---|---|
评价对象
- 评价的属性:{用户昵称、课程主标题、内容、综合评分、内容实用、简洁易懂、逻辑清晰、发布时间}
从三个维度对课程进行打分,内容实用、简洁易懂、逻辑清晰,进而可以对课程做一个综合评评。
每一个学习的课程都可以做一个综合评价,所以我们可以使用用户表的业务主键用户昵称和课程表的业务主键课程主标题来唯一标识
再看其他的属性,也都是和这两个属性是相关联的,所以我们就可以把这些属性放到一个表中,这个表就是
课程评价表:
用户昵称(pk) | 课程主标题(pk) | 内容 | 综合评分 | 内容实用 | 简洁易懂 | 逻辑清晰 | 发布时间 |
---|---|---|---|---|---|---|---|
从上面课程评价表的分析呀,可以发现一个问题,只有选择了一门课程的用户才可以评价,因此还需要一个让用户和所选的课程关联的表,也就是
用户选课表:
用户昵称(pk) | 课程主标题(pk) | 选课时间 | 累计听课时长 |
---|---|---|---|
总结
以上就是我们本实例涉及到的所有数据库对象,根据数据库设计范式要求,进行设计确定后的数据库逻辑存储结构,具体的设计范式呢,为了有个总体的认识,还是总结下:
逻辑设计总结
我们设计了那些表呢?首先来看下跟课程相关的一些表
- 课程表
- 课程章节表
- 课程小节表
- 课程同章节表的联系表
- 课程章节表同课程小节表的联系表
- 课程方向表
- 课程分类表
- 课程难度表
再来看下涉及到用户的有那些表
- 用户表
- 问答评论表
- 笔记表
- 评价表
- 用户选课表
以上就是我们根据数据库设计范式设计出来的三张表,这些表呢,已经尽可能的避免了数据库冗余,数据的更新、插入及删除异常这些问题。但是,这样的数据库设计我们使用起来是不是真正的方便呢,这样是不是就可以完成我们的数据库逻辑设计任务了呢?让我先来思考一个问题
范式化存在的问题
如何 获取一门课程包含所有章节和小节的信息?
如何查询呢?我们先来看所要获取的这些信息是如何存储的,
主标题 | 方向 | 章节 | 章节说明 | 小节 | 小节时长 |
---|---|---|---|---|---|
MySQL5.7复制功能实战 | 数据库 | MySQL复制基础 | 介绍了MySQL复制所需要的相关基础知识 | 课和简介 | 1:20 |
MySQL5.7复制功能实战 | 数据库 | MySQL复制基础 | 介绍了MySQL复制所需要的相关基础知识 | MySQL复制基础 | 6:20 |
上面的信息存储在课程表、课程章节表、课程小节表这三张表中,并且我们为了取出信息,还需要使用课程同章节的联系表以及课程章节同课程小节的联系表,那么这样呢,如果我们想要查询出信息就要关联5个表,如果学习过MySQL的SQL优化就会知道在MySQL中查询时关联的表越多查询的性能就会越差。而且课程的信息在项目中使用的还是很频繁的,用户每一次访问课程列表,都会进行这样的查询,如果查询很慢的话,必定会拖慢整个应用的查询性能。
所以,虽然已经完成表的范式化设计,但是出于业务查询性能的考虑,我们还要对经过范式化设计的表进行优化
优化性能的时候呢,通常或多或少的会违反一些范式化的要求,会存在一些数据的冗余,这种优化方式我们就叫做
反范式化设计
在反范式化设计的时候呢,我们通常情况下都是以空间换时间的。也就是说为了提高查询性能,而适度增加了数据的冗余,我们来分析下这几个表之间的关系
实例的表反范式化设计
课程章节表反范式化设计
课程表 和 章节表之间,存在一对多的关系,也就是说一门课程可以有多个章节,而一个章节只能属于一门课程,这样我们其实并不需要关联关系表,而是可以直接把课程章节表和课程同章节的联系表进行合并,合并之后的章节表就成了
课程主标题 | 课程章名 | 章节说明 | 章节编号 |
---|---|---|---|
这样课程章节表就包含了课程主表的业务主键课程主标题,这样虽然看似违反了第二范式,但是可以让我们在查询的时候少关联一个表,从而提高了查询的性能。
在一个需要被频繁查询的表上进行这样的反范式化设计呢,是很划算的。
课程小节表反范式化设计
看下课程章节表同课程小节表 之间的关系,是1<—N的关系,因此同样可以把课程章节表和课程同小节关系表进行合并
课程主标题 | 课程章名 | 小节名称 | 小节视频url | 视频格式 | 小节时长 | 小节编号 |
---|
再看如何获取一门课程包括所有章节和小节的信息
经过刚才的反范式化设计后,我们查询只需要再关联3个表就可以了
反范式化设计总结
反范式化设计的目的是为了减少查询需要关联的表的数据,从而提升数据的查询性能。需要注意的是,无论范式化设计还是反范式化设计,我们都要掌握一个度,不能过度进行范式化设计,同样也不能过度进行反范式化设计,这个度的标准呢,实际上就取决于我们需要关联表的数量。我们再来看下,进行反范式化设计后,我们的表会有那些变化,我们只是对课程章节表、课程小节表进行了反范式化设计,所以也只有这两个表的结构发生变化,课程相关的表呢由5个变成了3个,
- 课程表
- 课程章节表
- 课程小节表
其他的表并无变化,如记录课程属性集的那些表,课程方向表、课程分类表、课程难度表。
物理设计(地利):根据MySQL特点进行设计
总的来说,表的物理设计包括表的每一列使用的数据类型,以及如何对数据库中的库、表对象进行命名。但是对于MySQL数据库来说,还有一项工作是必须要做的,就是首选要选择你要使用的存储引擎,而存储引擎又决定了表中数据存储的机构。我们要如何设计一个表的数据引擎?面对这个问题,我们首先要知道的是可供我们选择的存储引擎
MySQL常见的存储引擎
引擎名称 | 事务 | 说明 | 特点 | 推荐度 |
---|---|---|---|---|
MYISAM | N | MySQL5.6之前的默认引擎,最常用的非事务型存储引擎 | 非事务的存储引擎;数据以堆表方式进行存储即存储的数据没有特定顺序,不像存储在距离索引的表中,数据按照索引的顺序存储,MYISAM表索引的叶子节点是直接指向物理地址,而不是距离索引的位置,对于大表的查找会提高一定的性能,但是其读写都会对数据加锁,在一个频繁读写的业务系统中,容易产生大量的阻塞 | 不推荐做为核心业务系统的存储引擎来使用 |
CSV | N | 以CSV格式存储的非事务型存储引擎 | 我们甚至可以直接编辑csv文件的内容来对表进行修改,由于其不支持事务,读写时会对表加锁,我们通常会用csv做不同系统中的数据交换 | 不建议做为核心业务系统的存储引擎来存储数据 |
Archive | N | 只允许查询和新增数据而不允许修改的非事务型存储引擎 | 由于它的适用场景比较特别,所以很多人并没有用过,但是Archive在一些特定场景确实可以帮助我们实现一些特定的业务需求。只能进行select和insert操作,不能进行delete和update操作,正如它的名字,我们可以将其用到归档或者日志这样只会新增而不会修改的业务场景中,并且使用Archive存储引擎存储数据占用的内存要比其他存储引擎小 | 适合归档或日志 |
Memory | N | 是一种易失性非事务型存储引擎 | 由于是存储在内存中的,索引读写会非常的快。我们知道数据库最大的性能是在读写性能上的,而memory引擎恰好会有很强的IO性能,那是不是很好呢?如果一旦重启,存储在Memory中的数据就会消失,所以它也是易失性的 | 不适合做为业务主数据的存储引擎,主要作用是在MySQL内部,比如执行一些大的SQL时候,中间一些比较大的数据结果集,并且中间数据在符合一定的条件下,会把中间数据结果集存储到Memory存储引擎的表中 |
INNODB | Y | 最常用的事务型存储引擎 | 是MySQL5.6后默认使用的存储引擎 | |
Memory | N | 是一种易失性非事务型存储引擎 | 由于是存储在内存中的,索引读写会非常的快。我们知道数据库最大的性能是在读写性能上的,而memory引擎恰好会有很强的IO性能,那是不是很好呢?如果一旦重启,存储在Memory中的数据就会消失,所以它也是易失性的 | 不适合做为业务主数据的存储引擎,主要作用是在MySQL内部,比如执行一些大的SQL时候,中间一些比较大的数据结果集,并且中间数据在符合一定的条件下,会把中间数据结果集存储到Memory存储引擎的表中 |
INNODB | Y | 最常用的事务型存储引擎 | 是MySQL5.6后默认使用的存储引擎 |
InnoDB存储引擎的特点
事务型存储引擎支持ACID
InnoDB存储引擎跟其它存储引擎像相比,最重要的是它是有一款事务型的存储引擎,其完全支持事务的原子性、一致性、隔离性和持久性这些特点,换句话说如果在我们业务场景中,需要把使用的数据用事务的话,最好的选择就是使用InnoDB存储引擎,并且需要在事务支持的场景中,一定不要混合使用事务型存储引擎和非事务型存储引擎。因为混合使用的话,在使用中一旦事务需要回滚,那么对于非事务型的存储引擎呢是无法回滚的,这样就破坏了事务的一致性要求,同时也破坏了事务完整性
数据按主键聚集存储
InnoDB中的数据在逻辑上是按照表中主键的顺序存储的,也就是说InnoDB中的主键是一种距离索引的主键。我们在选择使用什么样的列做为表中主键的时候,就要特别注意了,MYISAM使用的是堆方式进行存储,在具有距离索引的主键中,每一个非主键的叶子节点所指向的都是数据行中的主键,而不是数据行的物理存储位置,因此主键的大小就直接影响到了索引查找数据的性能。
另一方面,由于数据是按照逻辑主键的顺序排列进行存储,如果键的顺序经常变化,一定会造成数据的迁移,这样也会带来IO性能上的一些损耗。所以,一般来讲我们使用InnoDB做为存储引擎的表,都是建议使用一个自增Id来做为表的主键的,那么从这一点上来看,之前对表进行逻辑设计时,选择的业务主键并不适合做为InnoDB里的主键来使用,但是我们现在又需要使用Inn’o’DB存储引擎来存储我们的业务数据,这时候就要为每个表再加一个自增Id列来做为表的数据库主键,而之前所选择的业务主键必须再上面建立一个唯一索引,这样也可以同样保证其数据时唯一的
支持行级锁及MVCC
在进行数据读写操作时,可以对需要读取的数据行来加锁,而不会像MYISAM那样在整个表上枷锁,这无疑能大大加大我们存储引擎的数据并发处理能力
另外,Inn’o’DB还支持MVCC,也就是这个多版本的并发控制,可以进一步避免这个读写操作的互相阻塞
所以,Inn’o’DB非常适合那种高并发的读写混合的场景来使用
支持Btree和自适应Hash索引
所谓自适应Hash索引,就是由存储引擎对数据的统计信息在内存中自动建立的hash索引,这种索引只能用于等值查找,并且只能由InnoDb内部来进行维护,不需要DBA来进行过多的干预
支持全文和空间索引
MySQL5.6之后,InnoDB支持了全文索引。MySQL5.7之后版本中,InnoDB又支持了对空间索引。在MySQL5.7之前如果我们想使用MySQL的全文索引和空间索引呢,就必须使用MYISAM存储引擎,因为那时候只有MYISAM支持这两种索引。在MySQL5.7后,如果你想使用全文和空间索引就可以使用InnoDB做存储引擎了,这也就是说,在没有什么特殊需要的话,就可以完全使用Inn’o’DB来做存储引擎了。
根据InnoDB优化项目表逻辑结构
课程表
根据InnoDB存储引擎使用的是距离索引这一主键的特点呀,我们需要单独为每一个表增加一个自增Id列,来做为表的数据库主键使用。由于数据库主键通常要比业务主键小很多,所以表与表之间的关联呀,也可以通过数据库主键来运行。这样就可以比使用业务主键来关联查询会更有效率。
引入Id数据库主键之后呢,我们再来看一下现在的表结构有那些变化,同样我们先来看课程主表,增加了课程Id来做为表的数据库主键
课程Id (PK) | 主标题(非空唯一索引) | 副标题 | 方向ID | 分类ID | 难度ID | 上线时间 | 学习人数 | 时长 | 简介 | 需知 | 收获 | 讲师ID | 课程图片 | 综合评分 | 内容实用 | 简洁易懂 | 逻辑清晰 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | |||||||||||||||||
4 | |||||||||||||||||
5 |
上表,我们增加了课程Id来做为数据库主键,就像我们之前说的课程Id的数据就是1、2、3、4、5这样的自增Id,这样呢课程表的逻辑存储顺序就按照这个自增Id的顺序进行存储的了。我们可以保证后加入到表中的数据一定是可以排在表的末尾的,不会因为新增的数据而改变原来数据的这个逻辑存储顺序。
如果我们使用课程主表题来做为数据库主键的话,无疑是无法保证做到这一点的,同时为了课程主标题的唯一性,我们可以在课程主标题列上呢,建立一个唯一的索引。
另外,我们还更新了关联其他表所用到的列,比如把课程方向的方向名称改成了方向表的数据库主键方向ID,把分类名称更新成了分类表的数据库主键分类ID,难度名称呢更新成了难度表的数据库主键难度ID,同时我们把讲师昵称也更新成了用户表的用户ID即讲师ID
大家注意呀,这种数据库主键替换业务主键的话,是有利有弊的
- 好处是可以保证数据的一致性,比如讲师昵称,我们只要修改了用户表的用户昵称,当我们在查询课程信息的时候,由于是关联查询,所以在查询课程信息表的时候就可以同时查询到修改后的昵称
- 缺点是我们需要同用户表关联之后才可以获取到讲师昵称
所以,具体是要通过增加数据冗余性的方式,来减少关联,还是要保证数据的一致性,就要看业务的具体情况来定了,这里我们选择了增加表的关联来保证数据的一致性,这样可以减少数据冗余的一种方式。
课程章节表
同样在课程章节表中,也增加了章节ID,做为章节表的数据库主键来使用,同样在章节ID中存储也是1、2、3、4、5这样的数据。另外我们把章节表的相关联的课程ID,由于课程ID只是一个自增序列,比这个字符串列要占用存储空间小上很多,所以使用课程ID同课程表进行关联,要比使用课程主标题进行关联呢,性能会更好。
章节ID(PK) | 课程ID | 章节名称 | 章节说明 | 章节说明 | 章节编号 |
---|---|---|---|---|---|
1 | |||||
2 | |||||
3 | |||||
4 | |||||
5 |
课程小节表
课程小节表增加了自增序列的课程小节ID,同时课程名称、章节名称都替换成了课程表的课程ID,章节表的章节ID
小节ID(PK) | 课程ID | 章节ID | 小节名称 | 小节视频url | 视频格式 | 小节时长 | 小节编号 |
---|---|---|---|---|---|---|---|
1 | |||||||
2 | |||||||
3 | |||||||
4 | |||||||
5 |
课程方向表
使用课程方向ID做为数据库主键
课程方向ID(PK) | 课程方向名称 | 填加时间 |
---|---|---|
1 | ||
2 | ||
3 | ||
4 | ||
5 |
课程分类表
使用课程分类ID做为它的数据库主键
课程分类ID(PK) | 课程分类名称 | 填加时间 |
---|---|---|
1 | ||
2 | ||
3 | ||
4 | ||
5 |
课程分类表
使用课程难度ID做为它的数据库主键
课程难度ID(PK) | 课程难度名称 | 填加时间 |
---|---|---|
1 | ||
2 | ||
3 | ||
4 | ||
5 |
用户表
在用户表中,我们增加了用户ID,并且之前呢用户昵称在课程主表中也用到过,并且为了保证用户昵称的唯一性,我们还需要在用户昵称上增加一个非空的唯一索引
用户ID(PK) | 用户昵称 (非空唯一索引) | 密码 | 性别 | 省 | 市 | 职位 | 说明 | 经验 | 积分 | 关注人数 | 粉丝人数 |
---|---|---|---|---|---|---|---|---|---|---|---|
问答评论表
增加了自增ID评论ID,并且把这个同课程表、章节表、小节表相关联的业务主键都替换成了相应表的数据库主键,如课程ID、章节ID、小节ID,父评论ID实际是其所回复的评论的评论ID
评论ID | 父评论ID | 课程ID | 课程章节ID | 小节ID | 评论标题 | 用户ID | 内容 | 类型 | 浏览量 | 发布时间 |
---|---|---|---|---|---|---|---|---|---|---|
笔记表
同样引入了一个自增ID,并且把同其他表相关联的课程标题呀,章节名称呀,小节名称呀,都替换成了各自数据库主键
笔记ID | 课程ID | 课程章节ID | 小节ID | 笔记标题 | 用户ID | 笔记内容 | 发布时间 |
---|---|---|---|---|---|---|---|
评价表
实用课程ID、用户ID分别代替了原来的关联的业务主键
评价ID | 用户ID | 课程ID | 内容 | 综合评分 | 内容实用 | 简洁易懂 | 逻辑清晰 | 发布时间 |
---|---|---|---|---|---|---|---|---|
选课表
使用课程ID、用户ID分别代替了原来的关联的业务主键
选课ID | 用户ID | 课程ID | 选课时间 | 累计听课时长 |
---|---|---|---|---|
数据类型的特点
整数类型的特点
MySQL共有5种整数类型,按可以存储的整数值的范围从小到大排列,依次为
列类型 | 存储空间 | 属性 | 取值范围 | 直观值 |
---|---|---|---|---|
tinyint | 1字节 | SIGNED | -128~127 | 正负1百多 |
tinyint | 1字节 | UNSIGNED | 0~255 | 正2百多 |
smallint | 2字节 | SIGNED | -32768~32767 | 正负3万多 |
smallint | 2字节 | UNSIGNED | 0~65535 | 正6万多 |
mediumint | 3字节 | SIGNED | -8388608~8388607 | 正负8百多万 |
mediumint | 3字节 | UNSIGNED | 0~16777215 | 正1千6百多万 |
int | 4字节 | SIGNED | -2147483648~2147483647 | 正负21亿多 |
int | 4字节 | UNSIGNED | 0~4294967295 | 正42亿多 |
bigint | 8字节 | SIGNED | -9223372036854775808~9223372036854775807 | 正负9百多亿亿 |
bigint | 8字节 | UNSIGNED | 0~18446744073709551615 | 正1千多亿亿 |
浮点(实数)类型的特点
存储小数的数据类型我们称之为实数类型。实数类型和整数类型主要的不同呢,是实数类型可以存储数值的小数部分,但是它们又并不是只可以用来存储实数的,我们可以存储比bigint更大的,MySQL中共有3种实数类型:
列类型 | 存储空间 | 是否精确类型 |
---|---|---|
FLOAT | 4字节 | 否 |
DOUBLE | 8字节 | 否 |
DECIMAL | 每4个字节存9个数字,小数点占一个字节 | 是 |
非精确是什么意思呢?就是说存储在double、float类型中的数值小数部分可能并不精准,经过计算后可能与我们认为正确的值存在一定的偏差。
那么除了float、double之外,MySQL另外的实数类型DECIMAL类型则是很精确的数字类型,DECIMAL可以保证小数值的精确,还有其占用的存储空间比float、double两种类型要多的多。DECIMAL类型是每4个字节可以存储9个数字,并且小数点也会占用一个字节,以下面的小数类型来说:
123456789.987654321 = DECIMAL(18,9) 占用9个字节
在小数点前后有9个数字,那么存储就需要9个字节来存储,怎么算出来的呢?
我们知道DECIMAL每4个字节可以存储9位数字,所以小数点之前的123456789就会占用4个字节来存储,同样小数点后的987654321 9个数字也要占用4个字节来存储,再加上小数点的一个字节,所以一共就占用了9个字节。
另外,在MySQL5.0版本后,DECIMAL最多允许存放65个数字,不过这也应该足够了
对于以上三种数据类型如何选择,就根据我们实际的业务有很大的关系,从它们的特点呢,我们可以看出,和财务相关的数据,我们就应该使用精确的DECIMAL类型来存储,而对于其它的情况呢,我们就可以使用非精确的DOUBLE、FLOAT类型来进行存储
实数类型实战
我们实战演示,我们需要建立一个临时的数据库,
CREATE DATABASE test;
USE test;
-- 建立一个测试表
CREATE TABLE t (d1 double ,d2 decimal(10,3));
-- 向表t中插入测试数据
INSERT INTO t VALUES (11.2,11.2),(2.56,2.56),(9.01,9.01),(132.33,132.33);
-- 看一下测试表
SELECT * FROM t;
可以看到两列的值是一样的
d1 | d2 |
---|---|
11.2 | 11.200 |
2.56 | 2.560 |
9.01 | 9.010 |
132.33 | 132.330 |
接下来,我们在两列进行一些计算,让我们sum下d1,以及sum下d2列
SELECT SUM(d1),SUM(d2) FROM t
我们可以看到,第一列的值是155.10000000000002(n多个0后跟了个2),第2列是155.100,实际上值就应该是155.1,合起来就是155.1,这是DECIMAL它是这种精确的数据
DOUBLE呢,它除了155.1外,在最后还多了一个2,虽然说0.00000000000002吧,但是呢也这么说它是一种不精确的类型,它经过计算后,实际上是这种跟实际的值有出入的情况,这就是说为什么我们说DOUBLE、FLOAT我们觉得呢它是一种不精确的小数值
sum(d1) | sum(d2) |
---|---|
155.10000000000002 | 155.100 |
时间类型的数据
时间类型的数据呢,也是我们经常要在数据库中存储的一种数据。以我们的项目而言呀,比如像我们的注册时间、笔记的发表时间,还有这种小节的时长,这些呢都是时间类型的数据。接下来,我们来看看MySQL如何存储时间类型的数据。
MySQL中常用的时间类型主要有5种:
类型 | 存储空间 | 格式 | 范围 | 直观值 |
---|---|---|---|---|
DATE | 3字节 | YYYY-MM-DD | 从’1000-01-01’到’9999-12-31’ | 1000~9999年 |
TIME | 3-6字节 | HH:MM:SS[.微秒值] | 从’-838:59:59’到’838-59-59’ | 正负838小时 |
YEAR | 1字节 | YYYY | 从’1901’到’2155’ | 1901~2155年 |
DATETIME | 5-8字节 | YYYY-MM-DD HH:MM:SS[.微秒值] | 从’1000-01-01 00:00:00’到’9999-12-31 23:59:59’ | 1000~9999年 |
TIMESTAMP | 4-7字节 | YYYY-MM-DD HH:MM:SS[.微秒值] | 从’1970-01-01 00:00:00’ UTC 到’2038-01-19 03:14:07’ | 1970~2038年 |
- DATE:主要用于存储日期时间数据的日期部分,格式是年月日这样的格式
- TIME:存储日期的时间部分,对范围是不是有些奇怪?每天只有24个小时,为什么这个TIME可以存储-838-838小时之间的这么一个时间呢,其实TIME类型不仅可以存储一天的时间,还可以存储一个区间内的小时数这样的一个数据。比如我们可以存储06-02到06-21之间的小时,这样TIME的值就会大于24小时。可以看到TIME类型有3-6个字节的存储空间,为什么会这么大的区间呢?这实际上取决于TIME类型的定义,它是否会含有小数,也就是微秒值,它支持0-6的长度定义,当定义的长度为0时,是不含微秒数据,这时候就只占3个字节,在此基础上每多2位小数就会多占一位字节,最多可以存储6位小数秒数,也就是最多可以多占3个字节,加上原来占用的字节,总的字节会达到6个字节的大小。
- YEAR:通常我们存储年份数据,我们会使用整型或者字符串的类型来存储,但是在MySQL中,我们要存储1901到2155之间的年份,使用YEAR类型还是使用整型或字符串更合适?因为就算我们使用smallint存储也需要占用2个字节,使用字符串呢也需要占用4个字节,使用YEAR呢只需要占用1个字节的存储空间
- DATETIME:跟TIME类似,都可以存储小数微秒值
- TIMESTAMP:存储的时间格式跟DATETIME是相同的,不同的是TIMESTAMP只能存储格林尼治标准时间1970-01-01 …到2038-01-19 …之间的数据,并且其数据是包括了时区信息的,也就是相同的时间呢,在不同的时区下,可能会看到不同的时间
时间类型实战
CREATE TABLE t_timestamp(dt TIMESTAMP );
INSERT INTO t_timestamp SELECT now();
SELECT * FROM t_timestamp;
dt |
---|
2019-02-22 13:19:04 |
让我们更改下时区的设置,改成东10区
SET time_zone='+10:00';
SELECT * FROM t_timestamp;
dt |
---|
2019-02-23 04:19:04 |
我们可以看到,这个时间呢发生了一些变化,这就是TIMESTAMP这个时间类型一些特点,在不同时区下显示的值是不一样的。
同时,它的存储范围也是有限的,如果我们的存储范围允许使用的话,我们可以使用来进行存储,如果超出了,就可以使用DATETIME来存储。
字符串类型数据
我们工作中,最常用到的数据类型是字符串,我们差不多可以把各种类型存储到字符串类型中,在MySQL中常用的字符串类型有以下几种:
类型 | 范围 | 说明 | 直观值 |
---|---|---|---|
Char(M) | M=1~255个字符 | 固定长度 | |
VarChar(M) | 一行中所有varchar类型的列所占用的字节数不能超过65535个字节 | 存储可变长度的M个字符 | |
TinyText | 最大长度255个字节 | 可变长度 | |
Text | 最大长度65535个字节 | 可变长度 | 64k |
MediumText | 最大长度16777215个字节 | 可变长度 | 不超过16M |
LongText | 最大长度4294967295个字节 | 可变长度 | 不超过4G |
Enum | 集合最大数目为65535 | 只能插入列表中的值 |
- Char :定长的字符串类型,在定义时可以指定其所存储的字符串的长度,注意呀,这里说的是字符不是字节,因为在一个多字节的字符集中,一个字符可以占用多个字节,比如在MySQL8.0中默认的字符集就是UTF-8mb4,默认的就是一个中文占4个字节,而MySQL的Char类型最多可以存储不超过255个字符的一个字符串,这个M呢是指定这个字符串的一个宽度。之所以说Char是一个固定长度的,是因为只要我们定义了char的一个宽度M,无论是否存储M个字符,那实际存储的值都会占用M存储占用的空间
- VarChar:和Char不同的是它是一种可变长的数据类型,也就是说无论我们定义的宽度M是多少,在使用的时候都会占用这些字符实际占用的空间。但是使用VarChar类型需要注意的地方,在一个表中所有定义的所有VarChar类型的字节的宽度总和是不能超过65535个字节的,这个字节呢是定义的宽度M同字符集的字节数乘积之后产生的值,而不是使用字符数。如果我们定义了一个VarChar(10)的UTF-8mb4字符集下的话,那么它所占用的字节数是40个
- TinyText、Text、MediumText、LongText:注意单位是字节
- Enum :存储枚举类型的,最多可以定义65535个枚举值,存储时只能存枚举里的值
如何为数据选择合适的数据类型
接下来,看看在众多的数据类型中,为我们的数据选择出合适的数据类型,前面说过了,字符串类型几乎可以存下所有的数据,但是呢这绝不是一种好的选择,因为在选择一种数据类型在存储数据的时候呢,我们应该
- 优先选择符合存储要求的最小数据类型
比如对于一个非负整数3147483647,我们可以使用Unsigned int 来存储,这样比使用Bigint来存储节省了一倍的空间
再比如可以将字符串转化为整数进行存储,我们将ip地址转化为整数进行存储
INET_ATON('255.255.255.255') =4294967295;
INET_NTOA(4294967295)='255.255.255.255'
转化前需要使用15个字节来存储,转化为int后只要4个字节就可以,不过需要注意的是需要使用Unsigned属性来存储,这样才可以存下转化后的整数
- 谨慎使用ENUM,TEXT字符串类型
在实际工作中,发现很多人当不知道如何来存储数据的时候,很喜欢使用Text来存储,特别是对于存储一些像备注信息呀,说明信息呀这样的数据列的时候。而仔细又想一想,Text类型分为4种,TinyText、Text、MdiumText、LongText,而以最常见的Text类型为例,就可以存储下64k字节的这么多数据,用UTF8字符集呢,也可以存储将近2万多个汉字,而我们的备注或者说明很少可以写这么多字的,所以对于这一类型数据,一般使用VarChar类型也就足够了。
另外由于MySQL的内存临时表是不支持Text这样的大数据的,所以呢如果的查询类型中包括这样的数据类型的话,在进行排序等操作的时候就不能使用内存临时表,而必须要使用磁盘临时表,而且对于这一类数据,MySQL在读取数据的时候还需要进行二次查找,所以会使SQL的性能变的很差。但是呢,并不是说就不能使用这种数据类型了,如果在表中一定要使用Text类型的话,那么就建议将Text呢单独的分离到一个扩展表中单独存储,并且在查找时候一定不要使用SELECT * 这样的方式,而是只取出必要的列,在不需要这Text列时候就不要进行查询。
使用Text还有一点需要注意的是,因为MySQL对索引长度是有限制的,所以在Text类型上是不能进行全部索引的,而只能进行前缀索引。
并且Text类型的列上是不能有默认值的。
除了Text类型,使用中还需要注意一个是枚举类型。在其他关系型数据库中比如SQL Server中是并没有的。枚举本身呢也是一个字符串,但是内部却是以整数来存储的,前面我们说过如何选择类型最小的类型,实际上我们就有一种方式,就把字符串转为整数,在这一点上来看,枚举类型确实是一个不错的数据类型,是枚举类型的一个优点,也是很多人喜欢用枚举类型的原因。但是呢,枚举类型也存在一个很大的缺点,首先是我们使用枚举类型时,需要对枚举类型的值进行修改的话,就必须使用Alter语句,虽说对表的元数据的修改,可以很快的完成,但是在修改的时候,我们势必要对这个表加下元数据锁,这个时候也可能会产生一个很大的阻塞,所以说也是有一定的业务风险的 - 同财务相关的数值型数据,必须使用decimal类型
数据库设计规范,要求呢对于财务类的数据,比如商品的成本呀、价格呀、利润呀等等这样的数据时,必须要使用精确的浮点数decimal类型,因为使用decimal类型,可以保证在浮点运算时候不丢失精度
为项目选择合适的数据类型
为课程表选择合适的数据类型
列名 | 数据类型 |
---|---|
课程Id(PK) | int unsigned |
主标题(UK) | varchar(20) |
副标题 | varchar(50) |
课程方向ID | smallint unsigned |
课程分类ID | smallint unsigned |
课程难度ID | smallint unsigned |
上线时间 | datetime |
学习人数 | int unsigned |
课程时长 | time |
课程简介 | varchar(200) |
学习需知 | varchar(200) |
课程收获 | varchar(200) |
讲师ID | int unsigned |
课程主图片 | varchar(200) |
内容评分 | decimal(3,1) |
简单易懂 | decimal(3,1) |
逻辑清晰 | decimal(3,1) |
综合评分 | decimal(3,1) |
课程评分这里使用的是decimal存储的,当然这里也可以使用float,因为它并不是财务数据
课程章节表
列名 | 数据类型 |
---|---|
章节Id(PK) | int unsigned |
课程ID(UK) | int unsigned |
章节名称(UK) | varchar(50) |
章节说明 | varchar(200) |
章节编号 | tinyint(2) unsigned ZEROFILL |
章节编号是章节在课程中的顺序,这里使用的无符号tinyint,相信一个课程章节是不会超过255个,tinyint后面的(2)并不能影响具体的占用空间大小,只是标识章节编号这一列呢是2位数,而ZEROFILL呢保证在不足2位时补0
课程小节表
列名 | 数据类型 |
---|---|
小节Id(PK) | int unsigned |
章节Id(PK) | int unsigned |
课程ID(UK) | int unsigned |
小节名称(UK) | varchar(50) |
小节URL | varchar(200) |
视频格式 | enum(‘avi’,‘mp4’,‘mpeg’) |
小节时长 | time |
小节编号 | tinyint(2) unsigned ZEROFILL |
课程分类表
列名 | 数据类型 |
---|---|
课程分类Id(PK) | smallint unsigned |
分类名称(UK) | varchar(10) |
添加时间 | timestamp |
课程难度表
列名 | 数据类型 |
---|---|
课程难度Id(PK) | smallint unsigned |
难度名称(UK) | varchar(10) |
添加时间 | timestamp |
课程方向表
列名 | 数据类型 |
---|---|
课程方向Id(PK) | smallint unsigned |
方向名称(UK) | varchar(10) |
添加时间 | timestamp |
用户表
列名 | 数据类型 |
---|---|
用户Id(PK) int unsigned | |
用户昵称(UK) | varchar(20) |
密码 | char(32) |
性别 | char(2) |
省 | varchar(20) |
市 | varchar(20) |
职位 | varchar(10) |
说明 | varchar(100) |
经验值 | mediumint unsigned |
积分 | int unsigned |
关注人数 | int unsigned |
粉丝人数 | int unsigned |
讲师标识 | tinyint unsigned |
注册时间 | datetme |
用户状态 | tinyint unsigned |
问答评论表
列名 | 数据类型 |
---|---|
评论(PK) | int unsigned |
用户ID | int unsigned |
课程ID | int unsigned |
章节ID | int unsigned |
小节ID | int unsigned |
父评论ID | int unsigned |
评论标题 | varchar(50) |
评论内容 | text |
评论类型 | enum(‘问答’,‘评论’) |
浏览量 | int unsigned |
发布时间 | datetime |
笔记表
列名 | 数据类型 |
---|---|
笔记(PK) | int unsigned |
用户ID | int unsigned |
课程ID | int unsigned |
章节ID | int unsigned |
小节ID | int unsigned |
笔记标题 | varchar(50) |
笔记内容 | text |
发布时间 | datetime |
问答评论表
列名 | 数据类型 |
---|---|
评价(PK) | int unsigned |
用户ID | int unsigned |
课程ID | int unsigned |
内容评分 | decimal(3,1) |
简单易懂 | decimal(3,1) |
逻辑清晰 | decimal(3,1) |
综合评分 | decimal(3,1) |
发布时间 | datetime |
用户选课表
列名 | 数据类型 |
---|---|
选课ID(PK) | int unsigned |
用户ID | int unsigned |
课程ID | int unsigned |
选课时间 | datetime |
累计听课时间 | datetime |
数据库对象命名
如何为数据库对象命名简单易懂的名字,也是有一些技巧的:
- 所有数据库对象名称必须使用小写字符可选用下划线分割
MySQL数据库对对象的名称,在默认情况下是区分大小写的。特别是在Linux系统下,MySQL数据库存储呢是Linux系统下的一个文件,Linux系统本身就是大小写敏感的,所以MySQL数据库的库名、表名也是大小写敏感的 - 所有数据库对象的名称定义禁止使用MySQL保留关键字
如果我们的对象名称和MySQL关键字相同,那MySQL在进行语法解析的时候就会出错了,造成SQL的执行失败。对于MySQL有多少关键字,可以通过下面的网站进行查询,就是MySQL的官方网站
MySQL官网文档 - 数据库的对象命名要做到见名知义,并且不要超过32个字符
32个字符并不是说MySQL限制的最大长度,但是表名太长的话,不但在以后使用过程中会很不方便,同时还会增加网络存储的开销,所以就根据经验限制在32个字符。通常32个字符就可以满足大部分对象命名的要求了。
建议在表的名称最好包括数据库的名称,这样当我们使用很多数据库的时候,根据表名就可以判断出这个表属于那个库的
对项目中的库和表进行命名
列名 | 代码 | 数据类型 |
---|---|---|
课程Id(PK) | course_id | int unsigned |
主标题(UK) | title | varchar(20) |
副标题 | title_desc | varchar(50) |
课程方向ID | type_id | smallint unsigned |
课程分类ID | class_id | smallint unsigned |
课程难度ID | level_id | smallint unsigned |
上线时间 | online_time | datetime |
学习人数 | study_cnt | int unsigned |
课程时长 | course_time | time |
课程简介 | intro | varchar(200) |
学习需知 | info | varchar(200) |
课程收获 | harvest | varchar(200) |
讲师ID | user_id | int unsigned |
课程主图片 | main_pic | varchar(200) |
内容评分 | content_score | decimal(3,1) |
简单易懂 | level_score | decimal(3,1) |
逻辑清晰 | logic_score | decimal(3,1) |
综合评分 | score | decimal(3,1) |
课程章节表
列名 | 代码 | 数据类型 |
---|---|---|
章节Id(PK) | chapter_id | int unsigned |
课程ID(UK) | course_id | int unsigned |
章节名称(UK) | chapter_name | varchar(50) |
章节说明 | chapter_info | varchar(200) |
章节编号 | chapter_no | tinyint(2) unsigned ZEROFILL |
课程小节表
列名 | 代码 | 数据类型 |
---|---|---|
小节Id(PK) | sub_id | int unsigned |
章节Id(PK) | chapter_id | int unsigned |
课程ID(UK) | course_id | int unsigned |
小节名称(UK) | sub_name | varchar(50) |
小节URL | sub_url | varchar(200) |
视频格式 | video_type | enum(‘avi’,‘mp4’,‘mpeg’) |
小节时长 | sub_time | time |
小节编号 | sub_no | tinyint(2) unsigned ZEROFILL |
总结
- 数据库的逻辑设计规范
- MySQL的常用存储引擎及其选择方法
- MySQL的常用数据类型及其选择方法
- 如何为表选择适合的存储类型
- 如何为表取个好名