MySQL优化相关总结

前言

1.1 b+树介绍

       B+树是B-树的变体,也是一种多路搜索树,其定义基本与B-树同,除了:

  1. 非叶子结点的子树指针与关键字个数相同;
  2. 非叶子结点的子树指针P[i],指向关键字值属于[K[i], K[i+1])的子树(B-树是开区间);
  3. 为所有叶子结点增加一个链指针;
  4. 所有关键字都在叶子结点出现;

       b+树结构如下图所示:
在这里插入图片描述
       B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;

1.1.1 b+树的特性
  1. 所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
  2. 不可能在非叶子结点命中;
  3. 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
  4. 更适合文件索引系统;
1.1.2 b+树索引原理

在这里插入图片描述
       如上图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。

1.2 索引优化的策略

  • 组合索引中的最左前缀匹配原则
  • 主键用作外键一定要建索引
  • 对 where,on,group by,order by 中出现的列使用索引
  • 尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0
  • 对较小的数据列使用索引,这样会使索引文件更小,同时内存中也可以装载更多的索引键
  • 索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2019-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2019-05-29’);
  • 如果一个列是比较长的字符串,为了加快这种长列的查询速度,可以为其建立前缀索引参考
  • 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
  • 不要过多创建索引, 权衡索引个数与DML之间关系,DML也就是插入、删除数据操作。这里需要权衡一个问题,建立索引的目的是为了提高查询效率的,但建立的索引过多,会影响插入、删除数据的速度,因为我们修改的表数据,索引也需要进行调整重建
  • 对于like查询,”%”不要放在前面。
    SELECT * FROMhoudunwangWHEREunameLIKE’后盾%’ – 走索引
    SELECT * FROMhoudunwangWHEREunameLIKE “%后盾%” – 不走索引
  • 查询where条件数据类型不匹配也无法使用索引
  • 字符串与数字比较不使用索引;
    CREATE TABLEa(achar(10));
    EXPLAIN SELECT * FROMaWHEREa=“1” – 走索引
    EXPLAIN SELECT * FROM a WHERE a=1 – 不走索引
  • 正则表达式不使用索引,这应该很好理解,所以为什么在SQL中很难看到regexp关键字的原因

1.3 索引失效的场景举例

【1】select * from book 当语句中没有where子句时,那肯定是不走索引的。

【2】select * from book where name(书名,varchar类型) like ‘%中’,不走索引,只要%出现在第一个位置,那么就不走索引。

【3】select name from book where sell_num(销量,数值类型) > 100,\color{#5505ff}{数值类型进行不等操作时},不走索引。

【4】select name from book where sell_num/2=100,对销量进行表达式操作或者是函数操作的,不走索引。

可以将sql语句改为 select name from book where sell_num=200

【5】select name from book where author(作者,varchar类型)=‘tom’ or author=‘jack’,使用or进行连接的,也不走索引,\color{#ff05ff}{应改为}select name from book where author=‘tom’ union all select name from book where author=‘jack’

【6】select name from book where translator(译者,varchar类型) is null,即进行null值判断,不走索引,可以在插入书籍信息的时候,译者为null的话就存入0,之后使用select name from book where translator='0’来查询译者为null的书籍名。

【7】select name from book where version(版本,varchar类型)=1.0,即需要进行隐式类型转换的,这里是从varchar类型转化为浮点类型,不会走索引,将1.0改为‘1.0’,则走索引。

【8】在组合索引中的查询不满足最左前缀原则时,查询不走索引。如果我们对book表建立联合索引(a,b,c),那么以下的语句是走索引的

select * from book where a='x' ;

select * from book where a='x' and b='y' ;

select * from book where a='x' and c='z' ;

select * from book where a='x' and b='y' and c='z' ;

       而以下语句不走索引

select * from book where b='y' ;

select * from book where c='z' ;

select * from book where b='y' and c='z' ;

1.4 MySQL优化建议

1.4.1 查询优化策略
  • limit [offset],[rows] 大数据量情况下的分页优化
select * from t order by id LIMIT 90000,10;

       优化1:

// 主键子查询:
select * from t where id >= (select id from t order by id limit 90000,1) limit 10;

       优化2:

// 主键子查询:
select * from t where id between 90000 and 90010; 

       优化3:

// 主键子查询:
select * from t where id in (90001,90002,90003,90004,90005,90006,90007,90008,90009,90010); 

       核心思想:只要能快速返回主键id就有希望优化limit , 按这样的逻辑,百万级的limit 应该在0.0x秒就可以分完。

  • 避免使用 select *
    需要什么信息就查询什么信息,查询的列多了,查询速度肯定会慢。

  • 当只需要查询出一条数据的时候,要使用 limit 1
    比如要查询数据中是否有男生,只要查询一条含有男生的记录就行了,后面不需要再查了,使用Limit 1 可以在找到一条数据后停止搜索。

  • 优化where查询
    ①避免在where子句中对字段进行表达式操作

比如:selectfromwhere age*2=36; 建议改成 selectfromwhere age=36/2

       ②应尽量避免在 where 子句中使用 !=或<> 操作符,否则将引擎放弃使用索引而进行全表扫描

       ③应尽量避免在 where 子句中对字段进行 null 值 判断

       ④应尽量避免在 where 子句中使用 or 来连接条件

  • 慎用 in 和 not in
select id from t where num in(1,2,3) 
//建议改成
select id from t where num between 1 and 3
  • in和exists, not in和not exists的合理运用

       exists 和 not exists 的具体用法可参考该篇博客
       很多时候用 exists 代替 in 是一个好的选择:如查询语句使用了not in那么内外表都进行全表扫描,没用到索引,而not exists子查询依然能用到表上索引,所以无论哪个表大,用not exists都比not in要快。

select num from a where num in(select num from b) 
//建议改成: 
select num from a where exists(select 1 from b where num = a.num)

       区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询。所以INEXISTS\color{#9e0eff}{IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。}
       关于not in和not exists,推荐使用not exists,不仅仅是效率问题,not in可能存在逻辑问题。

  • 表关联时,被用来Join的字段应是相同的类型,且该字段应建索引。
    这样,MySQL内部会启动优化Join的SQL语句的机制。
1.4.2 其他优化策略
  • 建数据库表时,给字段设置固定合适的大小
    字段不能设置的太大,设置太大就造成浪费,会使查询速度变慢。
  • 字段尽量使用not null
  • 如果字段的取值是有限而且固定的,使用 ENUM ,而不是 VARCHAR
    比如“性别”,“国家”,“民族”, “省份”,“状态”或“部门”。因为在MySQL中,ENUM类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。
  • 作必要的垂直分割
    将常用和有关系的字段放在相同的表中,把一张表的数据分成几张表,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。
  • 用EXPLAIN 分析 SELECT 查询
    使用EXPLAIN,可以帮助了解MySQL是如何处理sql语句的,可以查看到sql的执行计划,就能更好的去了解sql语句的不足,然后优化。

1.5 SQL语句相关总结

  1. 查询条件on、where、having区别
  • ON和WHERE的区别
    主要与限制条件起作用的时机有关,ON根据限制条件对数据库记录进行过滤,然后生产临时表;而WHERE是在临时表生产之后,根据限制条件从临时表中筛选结果。
    因为以上原因,ON和WHERE的区别主要有下:
    1)返回结果:在左外(右外)连接中,ON会返回左表(右表)中的所有记录;而WHERE中,此时相当于inner join,只会返回满足条件的记录(因为是从临时表中筛选,会过滤掉不满足条件的)。
    2)速度:因为ON限制条件发生时间较早,临时表的数据集要小,因此ON的性能要优于WHERE。
  • HAVING和WHERE的区别:
    也是与限制条件起作用时机有关,HAVING是在聚集函数计算结果出来之后筛选结果,查询结果只返回符合条件的分组,HAVING通常出现在GROUP BY子句中。而WHERE是在计算之前筛选结果,如果聚集函数使用WHERE,那么聚集函数只计算满足WHERE子句限制条件的数据。
    在使用和功能上,HAVING和WHERE有以下区别:
    1)HAVING不能单独出现,只能出现在GROUP BY子句之中;WHERE即可以和SELECT等其他子句搭配使用,也可以和GROUP BY子句搭配使用,WHERE的优先级要高于聚合函数高于HAVING。
    2)因为WHERE在聚集函数之前筛选数据,HAVING在计算之后筛选分组,因此WHERE的查询速度要比HAVING的查询速度快。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章