目录
目录
一:问题场景
二:优化
1.查询计划
2.分析计划
连接类型
三:索引的使用与选择
1.索引的使用以及优化
(1) MYSQL中的索引原理
(2) 4中树结构在索引中的应用(附红黑树)
2、树结构选择合适的索引。
3、mysql中建立索引的规范
4、mysql的索引分类以及使用场景
(1)索引的总分类
(2)Mysql中的索引
(3)、建立合适索引,避免滥用索引。
四:优化SQL减低查询时间
1.使用limit关键字
2.添加合适的索引
3.避免全表查询
五:SQL优化建议
1.SQL从程序中查询到程序显示结果的过程
2.SQL层级优化
在一次项目中,出现一个SQL优化问题,问题场景就是,在用户登陆时,将用户的部门、岗位、角色以及权限(其中权限整合在菜单表中,每2个表都是用一个中间表关联)查询出来作为一个全局常量(MSYQL数据库)。
不多说,直接上SQL语句吧。
SELECT
a.fk_user_id AS "fk_user_id",
a.user_realname AS "user_realname",
a.user_name AS "user_name",
a.user_type AS "user_type",
a.sex AS "sex",
a.phone AS "phone",
a.password AS "password",
a.user_addr AS "user_addr",
a.avater AS "avater",
a.status AS "status",
fr.role_id as role_id,
fr.role_code as role_code,
fr.role_name as role_name,
fr.role_range as role_range,
fd.dept_id as dept_id,
fd.dept_name as dept_name,
fd.parent_id as dept_parent,
fd.parent_ids as dept_parents,
fd.company_name as company_name,
fp.post_id as post_id,
fp.post_name as post_name,
fp.post_split as post_split,
m.perms as perms
FROM fk_user a
LEFT JOIN fk_user_role fkur ON fkur.user_id = a.fk_user_id
LEFT JOIN fk_role fr ON fr.role_id = fkur.role_id
LEFT JOIN fk_role_menu rm ON rm.role_id = fr.role_id
LEFT JOIN fk_menu m ON m.menu_id = rm.menu_id
LEFT JOIN fk_dept fd ON fd.dept_id = a.dept_id
LEFT JOIN fk_user_post fup ON fup.user_id = a.fk_user_id
LEFT JOIN fk_post fp ON fp.post_id = fup.post_id
where a.user_name ='admin'
在使用本地数据库连接(localhost:3306)基本查询就消耗了0.270s左右。见下图:
很尴尬,实际上查询的几张表数据一共也就在250条左右(其中菜单表和角色菜单表大概在160条),这里我们再来看一下SQL的查询计划统计:
其中,我们看type一行的查询涉及到3个名词:ALL,ref,eq_ref。Type学名为连接类型,连接类型决定查询时间。
ALL:全表扫描,最耗时的查询,即使在查询出符合条件的数据,还要继续查询其余剩下的数据。(在本例场景中实际代码
已经控制了用户名唯一,那么在查询出该用户就应该停止继续往下查询,但是如果要查询所有的用户则查询类型肯定
是ALL)。
INDEX: 加索引的全表查询,比较耗时的一种查询,就是在查询字段上添加一个索引。众所周知Mysql中的索引结构为B+树。在
同等数据结构中,B+数据的时间复杂度为 log(2)n优化程度仅次于1,降低了树高(即查询表列的行数减少了),常见
的索引优化就是讲表数据按照b+树排列,较少全表查询的实际行数。
RANGE:范围搜索,可以看出在是将查询数据行数按照一定的条件控制,减少实际查询行数(一定是基于索引)。常见得IN,
BETWEEN等都是这种类型。
REF: 单条件不限查询。简而言之,就是将查询放在一定的范围内查询,允许重复查询(这里区别于全表在于使用的索引,虽然
不是唯一索引)。
REF_EQ:单条件唯一查询。与上面不同的是,我们知道查询结果唯一,在查询出结果就不在继续查询了,所以时间程度 <= REF
CONST: 主键条件查询。将主键放在where条件之后,会别MYSQL优化器存储为一个常量。
在上面分析可知,除了ALL没有用到索引,其他所有的类型都用到了索引。那么我们就先来看看mysql中的索引。
在Mysql5.7+,Mysql默认的存储引擎为InnoDB。该引擎有2中索引:B+树索引以及哈希索引,其中哈希索引是Mysql更具表的使用情况自动创建的,无法人为干涉。
在上面,我们说到InnoDB默认为B+树,其实在索引中同期的树结构还有B树,B-树,B*树,下面我们就谈谈几种树结构的特性。
B树:又称 二叉树结构。每一个节点拥有2个子节点,大于该节点走右节点,小于该节点做左边。如图所示:
通过图形我们可以看出3层树结构存储7个数据(图中为了方便看,第三层省了一个)。查询一次 最多走3层树,效率明显提高(可以看出其实存储的是数 字的情况下比较好比较,这也是为什么我们有时候强调分 布式环境中最好不要使用UUID作为主键的原因)。 b树的时间复杂度 O(logN)
B-树:多链路查找树结构。每一个节点拥有>2个子节点。通过二分法查询找到左右2个关键字然后去中间查询。如图
如图,如果我们需要查询E这个节点。首先确定E在DG中,但是又不是D和G,因此我们取DG中间节点去查询。在这里我们看到3层树结构存储了至少17个关键字。但是吧,不一定说明其效率高于B树,毕竟谁也说不清在去中间结构判断消耗的时间。
B+树:多链路近似二分法。B+树在每一个连接节点不会存储节点指针只存储节点关键字,每一个非连接子节点存储根节点和对应连接节点的数据。 B+树的时间复杂度为 log(M-1)N~log(M/2)N
实际上,B+树就是将对应链路数据下沉下沉到最后一层,链路上的其他只记录关键字,不记录对应的指针信息等。因此,在B+树查询中效率接近二分法而且比B+效率高。
B*树: b+树的变种,允许同级关键字可以向相邻的兄弟节点上放。简而言之,b+树的每一个节点存放的关键字容量有限,点满本节点就必须裂变出子节点。而B*树,在本节点点满后,可以问同层左右未满的节点存放,充分利用了空间。(但是b*树每一个节点的容量小于b+树)
毋庸置疑,B*树查询效率接近B+树,但是在同级别上,其空间利用率很高。当然,如果B+树在每一个节点存放数据相同,B+树层级一定<=B*树。
附:红黑树,
红黑树也是一种多链路树结构,每一条链路都是红黑相间,根节点为黑色。每一个子节点必然为2个即使为NULL也是需要该节点而且必为黑色。
红黑树的时间复杂度为:O(lgn)。
上面我们分析几种树结构,那么分析完了有什么作用呢?通过对应数据库的树结构,合理的设计表结构。比如Mysql的InnoDB的默认为B+树,那么在设计表结构时就应该往这个方面靠。
(1)命名规范:表名_字段名,需要加索引的字段,要在where条件中,数据量少的字段不需要加索引,如果where条件中是
OR关系,加索引不起作用,符合最左原则。
(2)约束规范:索引中Index和key。其中Key是数据库中的物理结构,即是约束,约束结构完整性,同时也是索引,包含外检
等。而Index则是数据库的虚拟结构,只负责实现辅助查询,他创建时会在表空间创建虚拟目录以助辅助。
这里,我们不谈如何去创建索引,只谈所有哪些?如何在合适的场景选择合适的索引。
索引分为聚簇和非聚簇索引。
聚簇索引:按照表行物理排序为顺序的,其多行索引查询的速度很快。(暂戏称为全文索引吧)
非聚簇索引:按照表字段顺序为顺序的(也就是说这是针对单行数据),其单行查询书读很快。
普通索引: 没有任何约束的索引,一般添加在常用的查询字段上。比如我们在存储文章到数据库中,其中标题为一个字段且这
个字段会被经常查询,那么就可以在该字段上添加一个索引。(当然这里只是举一个不合适的例子,一般针对文
章的存储不会放入Mysql数据库中,而是放入对应的搜索索引中的。)
唯一索引: 这个索引在普通索引上添加一个约束,当查询到该条数据时,查询就会停止然后返回结果。比如,在用户表中查询
用户名就可以在该字段上添加一个唯一索引。毕竟多数的代码设计都是保证了用户名唯一,不唯一当我没说。
全文索引: 在Mysql3.23+就支持了全文索引,当然这个索引目前只适用于Mysam并且在数据量特别大的情况下,生成全文索引
速度特别慢。
组合索引: 多个字段同时且多用查询时,建立查询。比如用户表中的用户名和密码这俩个字段,在登录时一定会诶查询,那么
我们针对username和pwd建立索引,最后生成username+pwd 和 pwd 2个索引,遵循最左原则。
在上面我们谈到索引建立的好处,但是索引并不是越多越好,索引的乱建会导致Mysql服务性能下降严重时会导致服务崩溃。
原因在于一个索引字段在新增/修改/删除,会在操作数据时 新增/修改/删除 索引目录。
在上面我们谈到了除了ALL之外,所有的类型的查询都用了索引,因此我们对索引进行讲解。现在我们回归优化SQL降低查询类型提高查询效率。(这里提供以下MYSQL的SQL语句性能分析文章)
limit关键字在很大方面控制Send_data的时长。具体可看这篇从零开始java数据库SQL优化(番外):SQL执行性能分析。
在对应的字段上添加合适的索引
也就是避免Type = ALL
(1)ORM框架解析参数,封装成SQL语句
(2)MYSQL接收到SQL语句,进行优化器编译
(3)执行SQL语句,将数据从I/O磁盘读取到内存中
(4)从带村中读取数据
(1)ORM框架解析从参数,封装SQL,这里离说实话没办法,现在ORM开源框架如Mybatis等都已经封装好
(2)MYSQL-SERVER解析编译。
解析过程,通过解析SQL参数,封装可查询的语句。这里其实对于一些where条件以及决定查询Type都是这个过程决定的
因此,避免全表查询的优化就在这一步。
编译过程,解析完成,开始对执行语句进行编译。这里可以优化,可以预编译SQL语句,比如添加视图,临时表或者存储过
程等让其直接执行预编译好的语句。
(3)I/O磁盘读取到内存中,这里优化就是索引级别的了,可以添加索引,即目录索引配合SQL查询。
(4)内存读取数据,这个没办法ORM框架已经封装好,顶多网络优化。