设计流程一览:
业务分析---------->逻辑设计--------------->物理设计---------->对像命名
我们这里以慕课网免费课前端为项目,进行数据库的结构设计,并非实际慕课网的数据库结构。
首先来看慕课网前端的页面,可以进行项目的需求总结:
可以先罗列出对象及对象的属性:
这里我们拿课程表来理解,宽表顾名思义就是将一个对象的所有属性等不同的内容全都放在一张表中存储。
使用宽表存储数据:
delete from ‘课程表’ where 方向=‘数据库’
但同时我们也删除了方向为数据库的所有课程,这就违反了我们的需求。
要求我们:表的所有字段都是不可再分的
如下图,联系方式这一字段还有手机,固话,邮箱这三个字段,所以违反了第一范式
我们可以修改成这样,这就符合了第一范式的设计规则:
要求我们:表中必须存在业务主键,且非业务主键全部依赖于业务主键(若是组合业务主键,非业务主键也必须依赖于全部组合列)
我们可以看到,这张表的业务主键为用户,章节和标题组合而成的复合主键,那么其他非业务主键必须依赖于这三列,但是用户积分这一列只依赖于用户这一列,所以这违反了第二范式的要求
我们可以将不存在依赖关系的列全部提取出来,单独放到一个表中,这就维护了第二范式的规则,同时我们也发现,如果业务主键只有一个列,那么这个表天生符合第二范式要求的
要求我们:表中的非主键列之间不能互相依赖
可以看到,上面这张表中,讲师职位列与讲师名是相互依赖的,与主键列无依赖关系,所以我们单独将这两列提取出来,形成一个单独的表
从课程对象来看:
从课程列表对象来看:
从用户对象,问答评论对象来看:
从课程评价上看:
以上就是对慕课网前端的所有表结构设计,里面运用了反范式化设计,使得查询性能更优。
对于MySQL数据库来说,首先要选择存储引擎,而存储引擎又决定了数据的存储结构,我们来看一下可供选择的存储引擎有哪些
1.是否支持行级锁 : MyISAM 只有表级锁(table-level locking),而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
2.是否支持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
3.是否支持外键(表的外键是另一表的主键, 外键可以有重复的, 可以是空值,用于关联查询中): MyISAM不支持,而InnoDB支持。
4.是否支持MVCC :仅 InnoDB 支持。应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一。
特点:
MVCC即多版本并发控制:MVCC使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能。
在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。 事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行的版本号进行比较,在可重读Repeatable reads事务隔离级别下:
通过MVCC,虽然每行记录都需要额外的存储空间,更多的行检查工作以及一些额外的维护工作,但可以减少锁的使用,大多数读操作都不用加锁,读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,也只锁住必要行。
推荐阅读:mysql-innodb-mvcc
MySQL INNODB MVCC深度分析
MVCC
在RR级别中,通过MVCC机制,虽然让数据变得可重复读,但我们读到的数据可能是历史数据,是不及时的数据,不是数据库当前的数据!这在一些对于数据的时效特别敏感的业务中,就很可能出问题。
对于这种读取历史数据的方式,我们叫它快照读 (snapshot read),而读取数据库当前版本数据的方式,叫当前读 (current read)
快照读:
当前读:这个状态下会为该行加锁,其他事务就需要等待该事务提交后才可对该行操作
在RR隔离级别的普通查询语句中,INNODB会默认执行快照读,所以当你如果在进行增、删、改完成之后,再进行SELECT操作,则此时读取到的是最新版本的数据。如果是在增、删、改之前进行了快照读SELECT操作,在增、删、改之后继续快照读SELECT操作,则读到的就是旧版本数据。
Session A :
select * from table where id = 1; // id = 1 xx = 'a'
Session B :
update table set xx = 'b' where id =1; // id = 1 xx = 'b'
Session A:
select * from table where id = 1 lock in share mode; // 读到新数据 xx = 'b'
select * from table where id = 1; // 读到旧数据 xx = 'a'
Session B :
update table set xx = 'b' where id =1; // id = 1 xx = 'b'
Session A :
select * from table where id = 1; // id = 1 xx = 'b'
select * from table where id = 1 lock in share mode; // 同上
课程表中:添加课程ID作为表的业务主键,其为自增ID,课程表的逻辑存储顺序则按照自增ID来存储的,可以保证后加入的数据一定是排在表的末尾的,不会破坏表的逻辑存储顺序,同时为了保证主标题的唯一性,可以在主标题这一列建立唯一索引(具有唯一索引的列不允许具有相同的行),除此之外,为了能关联其他表,将方向名称更改为方向表的业务主键方向ID,同样,分类表,难度表和用户表也是如此,利用业务主键替换业务的这种方式有利有弊,利是可以保持数据的一致性,比如更新讲师表中的讲师昵称,由于关联查询同时也更新了课程表中的讲师昵称,缺点是需要课程表关联用户表,增加了数据冗余,所以具体情况看业务需求。
同样,在其他表中,都设置一个自增ID作为业务主键,并且将关联表的列设置为关联表的业务主键ID。
一个字节占8个比特位,所以tinyint类型在有符号中取值范围为-128~127,无符号为0-255,所以在选择整数类型时,要选择符合的类型,才能达到节省存储空间的效果,比如存储两位整数应用tinyint类型。
DECIMAL(a,b):整数部分加小数部分总长度为a,小数部分长度为b
执行以下代码:
create database test;
use test;
create table t(d1 double,d2 decimal(10,3));
insert into t values (11.1,11.1),(22.2,22.2);
select sum(d1),sum(d2) from t;
所以处理财务报表之类的数据时,推荐使用DECIMAL数据类型
TIME:time类型数据能存储一段时间,所以范围才会如此大,而存储空间为什么是3到6 个字节呢?这与它的微秒值有关,如下图
微秒值每增加两位增加一个字节,最多可以有6位,所以最多加3个字节
YEAR:YEAR类型数据只占用一个字节,用来存储年份数据类型,所以当存储1901到2055年之间年份时,比用整数类型或字符串类型要节省空间。
TIMESTAMP:可以看到不同时区的时间,存储时间范围没有DATETIME广,以下通过一个例子演示:
CREATE TABLE t_timestamp(dt TIMESTAMP);
INSERT INTO t_timestamp SELECT NOW();//这里是东8区
SELECT * FROM t_timestamp;
SET time_zone='+10:00';//改为东10区
SELECT * FROM t_timestamp;
Char(M):这个类型只要定义了M个字符,无论是否存储了M个字符,都会占用M个字符的存储空间;
VarChar(M):这个类型是也会定义宽度M,字符集长度不能超过M,但在实际中只会占用实际需要的存储空间,需要注意的是,这里不能超过65535个字节,是M和实际字符集占用字节的乘积;
上图IP地址以字符串存储需15个字节,转成整数类型只需4个字节;