零、什么是数据库设计?
简单来说,数据库设计就是根据业务系统的具体需求,结合我们所选的DBMS(数据库管理系统),为这个业务系统构造最优的数据库存储模型。并建立好数据库中的表结构及表与表之间的关联关系的过程。使之能有效的对应用系统中的数据进行存储,并可以高效的对已经存储的数据进行访问。
- 关系型数据库管理系统:MySQL、Oracle、SQLServer、PgSql
- 非关系型数据库管理系统:Redis、MongoDB、Memcache
- 需求分析
1、数据是什么
2、数据有哪些属性
3、数据和属性各自的特点有哪些
特点举例:时效性和非时效性数据
(1)具有时效性的数据可以采取过期、清理或者归档方式处理(例如验证码等)
(2)增长很快数据量大但非核心数据
-
逻辑设计
使用ER图对数据库进行逻辑建模 -
物理设计
这一步开始需要考虑数据库管理系统,根据数据库自身的特点把逻辑设计转换为物理设计 -
维护优化
1、新的需求信息建表
2、索引优化
3、大表拆分
二、需求分析
实体关系
实体及实体之间的关系(1对1、1对多、多对多)
需求分析例子
以一个小型的电子商务网站为例,在这个电子商务网站的系统中包含了以下几个核心模块:用户模块、商品模块、订单模块、购物车模块、供应商模块。
- 用户模块
- 商品模块
- 订单模块
- 购物车模块
- 供应商模块
最终实体关系图(通过联系集可转换为实际应用关系集,下面ER图例子)
商品——供应商:一种商品可由多个供应商提供,一个供应商可提供多种商品
商品——订单:一种商品于存在多个订单,一条订单存在多个商品
商品——购物车:一个购物车存在多个商品,一种商品可存放在多个购物车中
订单——用户:一个用户可存在多个订单,一条订单只属于一个用户
购物车——用户:一个用户可拥有多个购物车,一个购物车只属于一个用户
三、逻辑设计
- 将需求转换为数据库的逻辑模型
- 通过ER图的形式对逻辑模型进行展示
- 和所选用的具体的DBMS系统无关
名词解释:
实体:显示世界中客观存在并可以被区别的事物。例如“一个学生”“一本书”
关系:一个关系对应通常所说的一张表
元组:表中的一行即为一个元组
属性:表中的一列即为一个属性;每一个属性都有一个名词,称为属性名
候选码:表中的某个属性组或属性,它可以唯一确定一个元祖(唯一值,筛选某行数据)
主码:一个关系有多个候选码,选定其中一个为主码(例如用户ID和身份证号都为唯一侯,选择ID为主码,即主键)
外码:一个属性(或属性组),它不是本表的候选码,但它是另一张表的候选码,则为外码(外键)
域:属性的取值范围(例如性别只有男和女)
分量:元组的一个属性值
表的设计范式:
注:后一范式的规则都是基于前一范式
第一范式(1NF):要求数据库中的表都是二维表。(参考二维数组和三维数组的区别)
(确保每列保持原子性)
第二范式(2NF):在第一范式基础上,表中不存在非关键字段(不唯一),对任一候选字段(唯一)的部分函数依赖,必须全部依赖于全部主键。
(确保表中的每列都和主键相关)
一张图解释范式定义:
图片来源: https://blog.csdn.net/weixin_43971764/article/details/88677688
举个例子:
上图主键为(商品名称,供应商名称)
问题,部分非关键字字段依赖于部分主键:
(商品名称)->(价格,描述,重量,商品有效期)
(供应商名称)->(供应商电话)
不符合第二范式要求的表存在下列问题:
- 数据冗余(例如上图部分非主键依赖于部分主键属部分函数依赖,造成另一部分主键和非关键字段重复出现)
- 插入异常(例如上图,当没有插入一厂的信息时,一厂的相关分类信息也就不存在)
- 更新异常(例如上图,一插入新厂可乐时,对应的可乐属性信息又插入一次,造成冗余)
- 删除异常(例如上图,当删除可乐信息时,一厂的相关分类信息也被删除)
第三范式(3NF):于第二范式基础上,表中不存在非关键字段(不唯一),对任意候选字段(唯一)存在传递函数。
(确保每列都和主键列直接相关,而不是间接相关)
举个例子:
上图的关键字段为商品名称,而后面两个字段存在以下传递函数依赖关系:
(商品名称)->(分类)->(分类描述)
不符合第三范式要求的表存在下列问题:
- 数据冗余(例如上图中,分类和分类描冗余字段,关于描述字段往往比较长,一般单独分一张表则只需一条描述记录)
- 数据的插入异常(例如上图,每次都需要插入一次分类和分类描述,没有插入某类商品时,对应分类也就不存在)
- 数据的更新异常(例如上图,单独更新商品描述信息同时则需要更新所有商品对应的描述)
- 数据的删除异常(例如上图,当删除所有商品时,对应的分类和描述也随之删除。此分类也就不存在)
BC范式(BCNF):在第三范式的基础上,如果是复合关键字,则复合关键字之间也不能存在函数依赖关系
(联合主键之间互相依赖)
举个例子:
存在下列关系因不符合BCNF要求:
供应商——>供应商联系人
供应商联系人——>供应商
-
当需求:需商品ID绑定供应商联系人,而供应商联系人绑定供应商时。则分表如下:
表1:应商联系人、商品ID、商品数量
表2:供应商联系人、供应商 -
当需求:需需商品ID直接绑定供应商。则分表如下:
- 插入和删除异常(如上图,如果饮料厂没有提供商品信息,也就找不到厂商相关信息)
- 更新异常和数据冗余(如上图,如果饮料厂新增商品,则会一直更新联系人信息,且如果厂商联系表多了联系方式、地址等字段就比较浪费空间)
四、物理设计
- 选择合适的数据库管理系统
Oracle、SQLServer、MySQL及PgSQL等。 - 定义数据库、表及字段的命名规范
- 根据所选的DBMS系统选择合适的字段类型
- 反范式化设计
为提高查询速度,可以在表中适当增加冗余。以空间换取时间
选择合适的数据库管理系统
MySQL常用的存储引擎
表及字段的命名规则
所有对象命名应该遵循下述原则:
- 可读性原则
使用大写和小写来格式化库对象名字以获得良好的可读性。(大小驼峰)
例如:使用CustAddress而不是custaddress - 表意性原则
对象的名字应该能够描述它所标识的对象。
例如:对于表,表名应体现所存储的内容,对于存储过程,应体现存储过程的功能 - 场面原则
尽可能少使用或不使用缩写
适用于数据库名之外的任一对象
字段类型的选择原则
列的数据类型一方面影响数据存储空间的开销,另一方面也会影响查询性能。
当一个列可以选择多种数据时,优先顺序如下:
数字类型——>日期或二进制类型——>字符类型
对于相同级别的数据类型,应优先选择占用空间小的数据类型
例如下面Birthday,最佳的选择类型是int
以上选择原则主要是从下面两个角度考虑:
- 在对数据进行比较(查询条件、join条件及排序)操作时:
同样的数据,字符处理往往比数字处理慢 - 在数据库中,数据处理以页(16k)为单位,列的长度越小,利于性能的提升。
char、varchar与nvarchar的选择
- 如果列中要存储的数据长度差不多是一致的,则应该考略用char(例如性别等);
否则应该考虑用varchar - 如果列中的最大数据长度小于50Byte,则一般也考虑用char。
- 一般不宜定义大于50Byte的char类型列。
char:固定长度,固定存储空间
varchar:除了存储数据的长度外,还需要额外的字节储存变长数据的字典,而在检索数据时还需要确定数据存储的起始位置
nvarcahr:无论中英文都是2个字节,所以当不小于50字节又为中文时选择nvarchar
utf-8字符类型:每1个字符占用3个字节,所以如果大于15个字符则一般考虑使用varchar存储
decimal、float与double如何选择
- decimal用于存储精确数据,而float只能用于存储非精确数据。
所以精确数据只能选择decimal类型,例如金额等 - 由于float的存储空间开销一般比decimal小
所以非精确数据优先选择float类型或double。
float:浮点型,含字节数为4,32bit,数值范围为-3.4E38~3.4E38(7个有效位),对最后一位四舍五入。
double:双精度实型,含字节数为8,64bit数值范围-1.7E308~1.7E308(15个有效位),对最后一位四舍五入。
decimal:数字型,128bit,不存在精度损失,常用于银行帐目计算。(28个有效位),对最后一位四舍五入。
注:float和double的相乘操作,数字溢出不会报错,会有精度的损失。
注:当对decimal类型进行操作时,数值会因溢出而报错。
时间类型如何存储
- 使用int来存储时间字段的优缺点
优点:字段长度比datetime小
缺点:使用不方便,要进行函数转换
限制:智能存储到2038-1-19 11:14:07,即2^32 - 需要存储的时间粒度
年 月 日 小时 分 秒 周
如果需要存储的时间为年,则选择year字段,只占一个字节
如果需要存储到秒,则使用timestamp字段
如何选择主键
- 区分业务主键和数据库主键
业务主键用于标志业务数据,进行表与表之间的关联;
数据库主键为了优化数据存储(当使用InnoDB引擎创建表,而表没有主键时,会生成6个字节的隐含主键(不可见不可读),这和它的排列特性有关,所以最好手动创一个主键(例如自增ID)) - 根据数据库的类型,考虑主键是否顺序增长
有些数据库是按主键的顺序逻辑存储的,同时自增主键所占用的长度也比较小 - 主键的字段类型所占空间要尽可能小
对于使用聚集索引方式存储的表,每个索引后都会附加主键信息
区别:聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致。
(聚集插入数据时会自动根据索引排序插入到任意位置,非聚集索引直接追加到最后位置)
聚集索引和非聚集索引: https://blog.csdn.net/jiadajing267/article/details/54581262
避免使用外键约束
- 避免数据导入或写入的效率。每写入一条约束都需要查询是否符合外键约束
- 增加维护成本
- 虽然不建议使用外键约束,但是相关联的列上一定要建立索引
避免使用触发器
- 降低数据导入的效率
- 可能会出现意想不到的数据异常
- 使业务逻辑变得复杂(人员交流不畅等)
触发器使用(类似TP5的事件,TP3.2的钩子): https://blog.csdn.net/Eastmount/article/details/52344036
关于预留字段
- 无法准确的知道预留字段的类型
- 无法准确的知道预留字段中所存储的内容
- 后期维护预留字段所要的成本,同增加一个字段所需要的成本是相同的
- 严禁使用预留字段
反范式化设计
为符合第三范式一般采取分表的方式。但在现代网站开发中,连表查询往往效率低下。甚至当出现分库情况时还可能需要跨库查询。所以采用反范式化适当的增加冗余,采取以空间(表的数据冗余)换取时间(读取效率)的方式优化查询效率。
修改前
上图虽然符合范式化,但是查询性能比较慢,查询订单信息需要采用多表查询
适当增加冗余字段
上图采用反范式化设计,查询订单信息不再需要连表查询,单表操作即可完成。
- 减少表的关联数量
- 增加数据的读取效率
- 反范式化一定要适度
维护设计
- 维护数据字典(对字段进行说明或注释等实现理解的方式)
数据字典: https://blog.csdn.net/qq_37023388/article/details/79061881 - 维护索引
随着数据量增长和需求查询变化,需要对老的索引进行删除建立新索引 - 维护表结构
随着需求变化,需对列的增加更改。 - 在适当的时候对表进行水平或垂直拆分
随着数据量的大量增加,可以对表进行拆分优化
如何维护数据字典
- 使用第三方工具对数据字典进行维护
- 利用数据库本身的备注字段来维护数据字典。例MySQL
- 导出数据字典
以下用的是Navicat Premium,可以换成任意图形化客户端
SELECT
COLUMN_NAME 列名,
COLUMN_COMMENT 名称 ,
COLUMN_TYPE 数据类型,
DATA_TYPE 字段类型,
CHARACTER_MAXIMUM_LENGTH 长度,
IS_NULLABLE 是否必填,
COLUMN_DEFAULT 描述
FROM
INFORMATION_SCHEMA.COLUMNS
where
-- developerclub为数据库名称,到时候只需要修改成你要导出表结构的数据库即可
table_schema ='litchi'
AND
-- article为表名,到时候换成你要导出的表的名称
-- 如果不写的话,默认会查询出所有表中的数据,这样可能就分不清到底哪些字段是哪张表中的了,所以还是建议写上要导出的名名称
table_name = 'tb_item'\
如何选择合适的列建立索引
- 出现在where从句,group by 从句,order by从句中的列
- 可选择性大的列要放到索引的前面
目前的SQL执行前都会经过SQL优化器进行重新编译,在编译过程中,SQL优化器会按照数据库中的索引和统计信息对查询列进行重新排序和优化。会自动选择适合的索引。索引目前对于where条件中的列和索引中的列就顺序可以不用过度苛刻 - 索引中不要包括太长的数据类型
数据库的数据是采用页的方式进行存储的,一页16K,在这限定的大小中,每页能够存储的数据行数越多,索引的查找速度相对也就越快。
对于长字符串数据需建立数据可采用前缀索引:
ALTER table 表名 add index title_pre(列名(16))
SQL优化器: https://my.oschina.net/u/1859679/blog/1586098
如何维护索引
注意事项
- 索引过多不仅会降低写效率,而且会降低读的效率
SQL优化器会根据索引信息和统计信息来选择适合SQL所使用的索引,而如果有太多可以使用的索引的话,SQL优化选择的过程会降低查询效率。 - 定期维护索引碎片
索引碎片整理: https://blog.csdn.net/weixin_33779515/article/details/85977181 - 在SQL语句中不要使用强制索引关键字(在select 后面的列名被/…/ 包含)
在MySQL中,由于一些老的索引已经不适用或者更名删除等,如果不知道这种情况就会出错。
如何维护表结构
- 使用workbench等软件在线变更表结构
- 对数据字段的维护
- 控制表的宽度和大小(垂直拆分)
数据库中适合的操作
- 批量操作 VS 逐条操作
适合的是批量操作,逐条操作适合在从句中完成。
批量操作: https://www.cnblogs.com/ryanzheng/p/8317978.html - 禁止使用Select *这样的查询
- 控制使用用户自定义函数
大量使用自定义函数会对索引的使用造成影响
自定义函数: https://blog.csdn.net/peng_666666/article/details/57497242 - 不要使用数据库中的全文索引(alter table 表名 add fulltext 索引名(列名)
全文索引需要另外建立索引文件,对索引进行维护。对中文的支持不是很好。可以使用专门的搜索引擎工具来完成。