数据库设计概要

文章目录

    • 第一范式(1NF)
    • 第二范式(2NF)
    • 第三范式(3NF)
    • BC范式(BCNF)
    • 反范式化
    • MySql常用的存储引擎
    • 表及字段的命名规则
    • 表的水平垂直拆分
  • MySQL数据库优化
    • 数据库优化的目的
      • 避免出现页面访问错误
      • 增加数据库的稳定性
      • 优化用户体验
    • MySql慢查询日志的开启方式和存储格式
      • 开启慢查询
      • 分析慢查询工具
      • 查找有问题的SQL
      • 分析SQL查询
      • Count()和Max()的优化方法
      • 子查询优化
      • 如何选择合适的列建立索引
    • 数据库结构优化
    • 建表规约
    • 索引规约
    • SQL语句
    • ORM映射
    • 参考资料

第一范式(1NF)

定义:数据库表中的所有字段都是单一属性,不可再分的。这个单一属性是由基本的数据类型所构成的

换句话说,第一范式要求数据库中的表都是二维表
数据库设计概要_第1张图片

第二范式(2NF)

定义:数据库的表中不存在非关键字段对任一候选字段的部分函数依赖

部分函数依赖是指存在着组合关键字中的某一关键字决定非关键字的情况

换句话说:所有单关键字段的表都符合第二范式

第三范式(3NF)

定义:第三范式是在第二范式的基础之上定义的,如果数据表中不存在非关键字段,对任意候选关键字段的传递函数依赖则符合第三范式。

数据库中的多个字段之间存在依赖关系就不符合第三范式

BC范式(BCNF)

定义:在第三范式的基础之上,数据库表中如果不存在任何字段对任一候选关键字段的传递函数依赖则符合BC范式

也就是说如果是复合关键字,则复合关键字之间也不能存在函数依赖关系

2个关键字只有其中一个起主导作用,不能相互起主导作用

反范式化

反范式化是针对范式化而言的,在前面介绍了数据库设计的第三范式,所谓的反范式化就是为了性能和读取效率的考虑而适当的对第三范式的要求进行违反,而允许存在少量的数据冗余,就是用空间来换取时间

  1. 减少了表的关联数量
  2. 增加数据的读取效率
  3. 反范式化一定要适度

MySql常用的存储引擎

存储引擎 事务 锁粒度 主要应用 忌用
MylSAM 不支持 支持并发插入的表级锁 SELECT,INSERT 读写操作频繁
MRG_MYISAM 不支持 支持并发插入的表级锁 分段归档,数据仓库 全局查找过多的场景
Innodb 支持 支持MCVV的行级锁 事务处理
Archive 不支持 行级锁 日志记录,只支持insert,select 需要随机读取,更新,删除
Ndb cluster 支持 行级锁 高可用性 大部分应用

表及字段的命名规则

  1. 可读性原则:使用大小写来格式化库对象
  2. 表意性原则:命名通俗易懂
  3. 长名原则:尽可能少使用或者不使用缩写

表的水平垂直拆分

  • 为了控制表的宽度可以进行表的垂直拆分
    1. 经常一起查询的列放在一起
    2. text,blob等大字段拆分出到附加表中
  • 为了控制表的大小可以进行表的水平拆分
    1. 通过Hash的方式来对表的主键进行拆分,mod(id)
    2. 前台用拆分的表,后台用汇总后的表

MySQL数据库优化

数据库优化的目的

避免出现页面访问错误

  • 由于数据库连接timeout产生页面5xx错误
  • 由于慢查询造成页面无法加载
  • 由于阻塞造成数据无法提交

增加数据库的稳定性

  • 很多数据库问题都是由于低效的查询引起的

优化用户体验

  • 流畅页面的访问速度
  • 良好的网站功能体验

MySql慢查询日志的开启方式和存储格式

开启慢查询

  • 是否开启慢查询日志

    show VARIABLES LIKE ‘slow_query_log’
    show VARIABLES LIKE ‘%log%’ 查看参数是否开启
    show VARIABLES LIKE ‘long_query_time’ 超过多久会被记录
    show VARIABLES LIKE ‘slow%’ 查看日志文件地址

  • 开启log_queries_not_using_indexes

    set global log_queries_not_using_indexes=on;
    set global slow_query_log_file=‘文件地址’
    set global long_query_time=1

  • 慢查询所包含的内容

数据库设计概要_第2张图片

分析慢查询工具

  • mysqldumpslow -t 10 slow.log
  • pt-query-digest slow.log >slow.log.report

查找有问题的SQL

  1. 查询次数多且每次查询占用时间长的SQL
  2. IO大的SQL
  3. 未命中索引的SQL

分析SQL查询

explain … \G
数据库设计概要_第3张图片

数据库设计概要_第4张图片

Count()和Max()的优化方法

select max(payment_date) from payment;
create index idx_paydate on payment(payment_date); 建立索引,直接查询最后一列
count(*)包含了null值,所以查询的数量会不一样

子查询优化

通常情况下,需要把子查询优化为join查询,但在优化时要注意关联键是否有一对多的关系,要注意重复数据

当有重复数据时,使用join查询会显示重复数据,可以使用distinct

如何选择合适的列建立索引

  1. 在where,group by,order by,on从句中出现的列

  2. 索引字段越小越好

  3. 离散度大的列放在联合索引前面

  4. 索引有利于查操作,不利用写操作,但是过多的索引都有影响

  5. 查找重复及冗余的索引
    数据库设计概要_第5张图片

  6. 删除不使用的索引

数据库结构优化

  1. 尽量少用text字段,非用不可时最好考虑分表
  2. 使用bigint来存储IP地址,利用INET_ATON(),INET_NTOA()函数来进行转换
  3. 使用int来存储日期时间,利用FROM_UNIXTIME(),UNIX_TIMESTAMP()两个函数来进行转换

建表规约

  1. 表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint

    说明:任何字段如果为非负数,必须是 unsigned。
    注意:POJO 类中的任何布尔类型的变量,都不要加 is 前缀,所以,需要在设置从 is_xxx 到 Xxx 的映射关系。数据库表示是与否的值,使用 tinyint 类型,坚持 is_xxx 的命名方式是为了明确其取值含义与取值范围。
    正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除

  2. 表名、字段名必须使用小写字母或数字禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑

  3. 表名不使用复数名词

  4. 禁用保留字,如 desc,range,match,delayed,by等,请参考 MySQL 官方保留字

  5. 主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名

  6. 小数类型为 decimal,禁止使用 float 和 double

  7. 如果存储的字符串长度几乎相等,使用 char 定长字符串类型

  8. varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索引效率

  9. 表必备三字段:id, gmt_create, gmt_modified

  10. 表的命名最好是加上“业务名称_表的作用”

  11. 库名与应用名称尽量一致

  12. 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释

  13. 字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
    1)不是频繁修改的字段。
    2)不是 varchar 超长字段,更不能是 text 字段

  14. 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表

  15. 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度

索引规约

  1. 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引

  2. 超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引

  3. 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可

  4. 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。

  5. 如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能

    正例:where a=? and b=? order by c; 索引:a_b_c
    反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引a_b 无法排序

  6. 利用覆盖索引来进行查询操作,避免回表

  7. 利用延迟关联或者子查询优化超多分页场景

  8. SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts最好。

    说明:
    1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
    2)ref 指的是使用普通的索引(normal index)。
    3)range 对索引进行范围检索。
    反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别比较 range 还低,与全表扫描是小巫见大巫。

  9. 建组合索引的时候,区分度最高的在最左边。
    正例:如果 where a=? and b=? ,如果 a 列的几乎接近于唯一值,那么只需要单建 idx_a索引即可。
    说明:存在非等号和等号混合时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么即使 c 的区分度更高,也必须把 d 放在索引的最前列,即索引 idx_d_c

  10. 防止因字段类型不同造成的隐式转换,导致索引失效

  11. 创建索引时避免有如下极端误解:
    1)宁滥勿缺。认为一个查询就需要建一个索引。
    2)宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。
    3)抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。

SQL语句

  1. 不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
    说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行
  2. count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。
  3. 当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为NULL,因此使用 sum()时需注意 NPE 问题
  4. 使用 ISNULL()来判断是否为 NULL 值。
    说明:NULL 与任何值的直接比较都为 NULL。
    1) NULL<>NULL 的返回结果是 NULL,而不是 false。
    2) NULL=NULL 的返回结果是 NULL,而不是 true。
    3) NULL<>1 的返回结果是 NULL,而不是 true
  5. 在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句
  6. 不得使用外键与级联,一切外键概念必须在应用层解决
  7. 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性
  8. 数据订正(特别是删除、修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句
  9. in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内

ORM映射

  1. 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
    说明:

    1)增加查询分析器解析成本。

    2)增减字段容易与 resultMap 配置不一致。

    3)无用字段增加网络消耗,尤其是 text 类型的字段。

  2. POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射

  3. 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个 POJO 类与之对应

  4. sql.xml 配置参数使用:#{},#param# 不要使用${} 此种方式容易出现 SQL 注入

  5. BATIS 自带的 queryForList(String statementName,int start,int size)不推荐使用。
    说明:其实现方式是在数据库取到statementName对应的SQL语句的所有记录,再通过subList取 start,size 的子集合。
    正例:Map map = new HashMap<>();
    map.put(“start”, start);
    map.put(“size”, size);

  6. 不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。
    说明:resultClass=”Hashtable”,会置入字段名和属性值,但是值的类型不可控。

  7. 更新数据表记录时,必须同时更新记录对应的 gmt_modified 字段值为当前时间。

  8. 不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储

  9. @Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。

  10. 中的 compareValue 是与属性值对比的常量,一般是数字,表示相等时带上此条件;表示不为空且不为 null 时执行;表示不为 null 值时执行

参考资料

  1. 阿里巴巴开发规范.pdf
  2. 慕课网上的视频,数据库设计那些事

你可能感兴趣的:(数据库设计概要)