mysql优化-索引选择及查询条件优化

    随着项目的运行,数据量的累加,系统就会变得越来越慢,优化成为必然。主流的优化方式有开源、节流,开源为调整服务器配置,节流为提升系统的运行效率。其中mysql的查询占有较高的时间复杂度,以下会介绍mysql的优化方法。
     优化原则:
    1、面向相应时间
          指在一定的工作负载下尽可能的降低相应时间
    2、定位时间用在什么地方并进行优化
 
    优化思路:
 
    优化方式:
一、数据类型优化
    #to be continue
 
 
 
二、索引的行程过程
C1
c2
c3
5
3
Y
4
4
A
3
9
D
8
7
A
20
2
E
10
4
O
1
4
U
 
 
create index idx_t on t(c1);
 
    1、创建叶子节点
        1)提取数据(索引列+行记录地址或主键)
        2)将提取的数据进行排序,然后存入列中
        
    2、创建页目录
        1)提取数据(每个页中的最小记录+页号)
        2)排序(页内、页间)
         
    3、创建根节点
         
    查询的逻辑过程如下图,如果索引数中包含全部select的字段,则查询完索引树后即可返回结果,称之为索引覆盖;如果索引树中不完全包含select的字段,则查询完索引树之后需要回表查询。
        
        
        索引树的检索过程
            1)确定where条件中是否含有索引的前导列(第一列),有则进行2,否则停止索引检索
            2)在索引树中进行检索。检索规则按照所建立的索引顺序,也where中的顺序无关,比如建立索引create index idx_t on t(c1,c3,c2);  然后 select * from table where c3=‘A’ and c2=3 and c1=1;  检查顺序为c1>c3>c2
        
        组合索引命中情况:
            建立索引(c1,c2,c3)
            全匹配(完美命中)    where c1=2 and c2=2 and c3=3;
            前导列匹配(只能够命中前导列)    where c1=2 and c3=3;
            不包含前导列(不能命中)    where c2=2 and c3=3;
 
 
 
 
    
 
三、索引优化
·   1)通过索引的选择性确定该索引是否合理(计算值>70%)
        SELECT * FROM employees WHERE first_name='Martial' AND last_name='DuCasse';
        1.单列的选择性:distinct first_name)/count(*) from employees;  --0.45%
               select count(distinct last_name)/count(*) from employees;   --0.58%
        2.组合列的选择性:
               select count(distinct(concat(first_name,last_name)))/count(*) from employees  --99.32%  --30
    2)创建组合索引时,根据最左原则,选择性高的列为前导列。因此1)的案例创建语句为:create index idx_name01 on employees(last_name,first_name);
    3)每个索引的叶子节点都是又索引列+主键组成。
    4)当索引列长度过长,且列前缀的选择性接近全列索引,可以用前缀索引代替,因为索引变短能减少索引的叶子节点,从而降低内存的负载。建立语句:create index idx_name02 on employees(last_name,first_name(4));
    5)innodb默认聚蔟索引,根据主键查询条目时不用回表,叶子节点就是行记录。
    6)一个索引包含了所有需要查询的字段,则成为索引覆盖,即只需扫描索引而无需回表。使用了索引覆盖在explain的extra列为using index。
 
 
 
四、EXPLAIN的使用
    使用方式:explain select * from t1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
id:
    Id如果相同,可以认为是一组,从上往下顺序执行;
    在所有组中,id值越大,优先级越高,越先执行;
 
Select_type:
    主要有SIMPLE、PRIMARY、SUBQUERY、DERIVED、UNION、UNION RESULT。分别用来表示查询的类型,主要用户区别普通查询、联合查询、子查询的复杂查询
    SIMPLE  :    简单的select查询,查询中不包含子查询或者UNION
    PRIMARY    :    查询中若包含任何复杂的字部分,最外层查询则被标记为PRIMARY
    SUBQUERY    :    在select或where列表中包含的子查询
    DERIVED    :    在from列表中包含的子查询被标记为derived(衍生),mysql会递归执行这些子查询,把结果放在临时表中
    UNION    :    若第二个select出现在union之后,则被标记问union;若union包含在from子句的子查询中,外层select将被标记为:derived
    UNION RESULT     :     从union表获取结果的select
 
type:
    Type所显示的是查询使用了哪种类型,包括all、index、range、ref、eq_ref、const,system、null
    从好到坏排行:system > const > eq_ref > ref > range > index > all
    system    :    系统表专用,可以忽略
    const    :    用于比较primary key或unique index,只通过索引一次就能找到
    ed_ref    :    唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
    ref    :    非唯一碎银扫描
    range    :    只检索给定范围的行,使用一个索引来选择行,key列显示使用了哪个索引,一般就是使用between、<、>、in等where查询。
    index    :    full index scan,index与all区别为index类型只遍历索引树,这通常比all快,因为索引文件通常比数据文件下。
    all    :    full table scan 遍历权标以找到匹配的行
 
extra:
    额外信息
    using filesort    :    说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,mysql中无法利用索引完成的排序操作成为文件排序。效率评分为差
    using temporary    :    使用了用临时表保存中间结果,mysql在对查询结果排序使用临时表。常见于排序ordr by和分组查询group by。效率评分为很差
    using index    :    标识相应的select操作中使用了覆盖索引,避免访问了表的数据行,效率不错。如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有出现using where,表明索引用来读取数据而非执行查找动作。
    using where    :    表明使用where过滤
    using join buffer    :    表明使用了链接缓存,比如说在查询的时候,多表join的次数非常多,name将配置文件找那个的缓冲区的join buffer调大一些。
    
 
五、sql语句的新特性(我也看不懂,纯复制)
    1、ICP(Index Condition Pushdown Optimization)
        描述:在存储引擎层通过索引列提前过滤掉数据,从而减少服务器层访问的数据量,同时也减少存储引擎访问基表的次数
    使ICP的用条件
        1. optimizer_switch= 'index_condition_push down=on';
        2. 只能用于二级索引。
        3. explain显示的执行计划中type值为range、 ref、 eq_ref或者ref_or_null,且不是通过索 引覆盖获得查询结果。
        4. ICP可以用于MyISAM和InnnoDB存储引擎, 包括分区表(5.7)
 
        
    2、Nested-Loop Join
        1)SNLJ(Simple Nested Loop Join)
            
        2)INLJ(Index Nested Loop Join)
            
        3)BNL(Block Nested Loop)
            
         MySQL使用Join Buffer有以下要点:
        1. set optimizer_switch= 'block_nested_loop=on';
        2. join_buffer_size变量决定buffer大小。
        3. 只有在type类型为all, index, range的时候才可以 使用BNL。
        4. 在join之前就会分配join buffer, 在query执行完 毕即释放。
    
 
    3、MRR(multi-Range Read)
        描述:MTT(多范围读),他的作用针对二级索引的查询,将随机IP转化为顺序IO,提高查询效率。
         使用MRR条件
        1. optimizer_switch= 'mrr=on,mrr_cost_bas ed=off' 表示总是开启MRR优化。
        2. optimizer_switch= 'mrr=on,mrr_cost_bas ed=on' 表示基于成本控制是否使用MRR优化。
        3. explain显示的执行计划中type值为range、 ref或eq_ref。
        4. read_rnd_buffer_size 用来控制键值缓冲区 的大小。
 
         MRR未开启时
        MySQL(Innodb) 针对基于二级索引的查询策略 是这样的:
        1. 先根据where条件中的二级索引获取索引列与 主键的集合。
        2. 通过第一步获取的主键来获取对应的行记录。
         MRR开启时
        MySQL(Innodb) 针对基于二级索引的查询策略 是这样的:
        1. 先根据where条件中的二级索引获取索引列与主键 的集合。
        2. 将集合放在buffer里面,然后对集合按照 pk_column排序,得到新的集合。
        3. 利用已经排序过的集合回表取数,此时是顺序IO。
 
 
    4、BKA(Batched Key Access)
            描述: 多表关联(索引) 时,将外层循环的结果集存入join buffer,内存循环的数据通过索引与整个buffer中 的记录做比较。
       BKA的算法
            1. 多表关联时,将外层循环的结果集存入join buffer。
            2. BAK根据buffer中的数据与内表的关联条件去 内表获取相关索引,并将索引发送到MRR接口。
            3. MRR把收到的索引,根据主键进行重新排序, 然后再回表读取行记录。
            4. 返回结果集给客户端。
             BKA优点
            使用join buffer,减少了对内表的遍历次数 
            使用MRR,对内表的随机IO转换为了顺序IO
 
 
 
 
六、SQL语句优化
    1)sql语句优化的方向已命中索引为主。总所周知,建立了索引也不一定能命中,没有命中的索引就毫无意义了,而且会占用内存,可用explain工具查看索引使用情况。
    2)其次,在实现功能的情况下尽量减少数据的遍历次数,基本体现在explain 的 rows返回。
    3)大数据分页查询
            limit offset,N的时候,随着offset增加,语句耗时也在增加,因为mysql取offset+N行记录,然后放弃前offset行,只返回N行记录。当offset特别大的时候,效率非常低下。
            解决方案有几种:
                1、利用索引覆盖,查询信息少的时候可用,当查询的列信息过多也不甚理想。
                2、建立where索引,先走索引覆盖查询出分页的主键,然后根据主键走聚蔟索引查询出所需信息,语句:select * from employees m inner join(select emp_no from employees limit 100000,10) s on s.emp_no=m.emp_no;
                案例对比:
 
                        #普通分页
                        select * from employees limit 100000,10; --0.17s
                        select * from employees limit 600000,10;  --0.46s
                        select * from employees limit 1200000,10; —1.31s
 
                        #通过关联获取数据
                        select * from employees m inner join(select emp_no from employees limit 100000,10) s on s.emp_no=m.emp_no;  --0.08      
                        select * from employees m inner join(select emp_no from employees limit 600000,10) s on s.emp_no=m.emp_no; --0.21
                        select * from employees m inner join(select emp_no from employees limit 1200000,10) s on s.emp_no=m.emp_no; --0.46
 
                
 
    
 
 
Mysql 查询案例:
 
在网上看到了不少mysql查询优化的题目,我从中摘抄了几题有意思的。
 
创建数据表
create table student(sid varchar(6), sname varchar(10), sage datetime, ssex varchar(10));
insert into student values('01' , '赵雷' , '1990-01-01' , '男');
insert into student values('02' , '钱电' , '1990-12-21' , '男');
insert into student values('03' , '孙风' , '1990-05-20' , '男');
insert into student values('04' , '李云' , '1990-08-06' , '男');
insert into student values('05' , '周梅' , '1991-12-01' , '女');
insert into student values('06' , '吴兰' , '1992-03-01' , '女');
insert into student values('07' , '郑竹' , '1989-07-01' , '女');
insert into student values('08' , '王菊' , '1990-01-20' , '女');    
 
 
create table sc(sid varchar(10), cid varchar(10), score decimal(18,1));
insert into sc values('01' , '01' , 80);
insert into sc values('01' , '02' , 90);
insert into sc values('01' , '03' , 99);
insert into sc values('02' , '01' , 70);
insert into sc values('02' , '02' , 60);
insert into sc values('02' , '03' , 80);
insert into sc values('03' , '01' , 80);
insert into sc values('03' , '02' , 80);
insert into sc values('03' , '03' , 80);
insert into sc values('04' , '01' , 50);
insert into sc values('04' , '02' , 30);
insert into sc values('04' , '03' , 20);
insert into sc values('05' , '01' , 76);
insert into sc values('05' , '02' , 87);
insert into sc values('06' , '01' , 31);
insert into sc values('06' , '03' , 34);
insert into sc values('07' , '02' , 89);
insert into sc values('07' , '03' , 98);
create table course(cid varchar(10),cname varchar(10),tid varchar(10));
insert into course values('01' , '语文' , '02');
insert into course values('02' , '数学' , '01');
insert into course values('03' , '英语' , '03');
 
 
create table teacher(tid varchar(10),tname varchar(10));
insert into teacher values('01' , '张三');
insert into teacher values('02' , '李四');
insert into teacher values('03' , '王五');
 
    
查询平均成绩大于等于 60 分的同学的学生编号和学生姓名和平均成绩
select s.sid, sname, avg(score) as avg_score
from student as s, sc
where s.sid = sc.sid
group by s.sid
having avg_score > 60;
 
 
+------+-------+-----------+
| sid  | sname | avg_score |
+------+-------+-----------+
| 01   | ??    |  89.66667 |
| 02   | ??    |  70.00000 |
| 03   | ??    |  80.00000 |
| 05   | ??    |  81.50000 |
| 07   | ??    |  93.50000 |
+------+-------+-----------+
5 rows in set (0.00 sec)
 
 
查询平均成绩大于等于 60 分的同学的学生编号和学生姓名和平均成绩
select s.*, rank_01, rank_02, rank_03, rank_total
from student s
left join (select sid, dense_rank() over(partition by cid order by score desc) as rank_01 from sc where cid=01) A on s.sid=A.sid
left join (select sid, dense_rank() over(partition by cid order by score desc) as rank_02 from sc where cid=02) B on s.sid=B.sid
left join (select sid, dense_rank() over(partition by cid order by score desc) as rank_03 from sc where cid=03) C on s.sid=C.sid
left join (select sid, dense_rank() over(order by avg(score) desc) as rank_total from sc group by sid) D on s.sid=D.sid
order by rank_total asc;
 
+------+-------+---------------------+------+---------+---------+---------+------------+
| sid  | sname | sage                | ssex | rank_01 | rank_02 | rank_03 | rank_total |
+------+-------+---------------------+------+---------+---------+---------+------------+
| 08   | ??    | 1990-01-20 00:00:00 | ?    |    NULL |    NULL |    NULL |       NULL |
| 07   | ??    | 1989-07-01 00:00:00 | ?    |    NULL |       2 |       2 |          1 |
| 01   | ??    | 1990-01-01 00:00:00 | ?    |       1 |       1 |       1 |          2 |
| 05   | ??    | 1991-12-01 00:00:00 | ?    |       2 |       3 |    NULL |          3 |
| 03   | ??    | 1990-05-20 00:00:00 | ?    |       1 |       4 |       3 |          4 |
| 02   | ??    | 1990-12-21 00:00:00 | ?    |       3 |       5 |       3 |          5 |
| 04   | ??    | 1990-08-06 00:00:00 | ?    |       4 |       6 |       5 |          6 |
| 06   | ??    | 1992-03-01 00:00:00 | ?    |       5 |    NULL |       4 |          7 |
+------+-------+---------------------+------+---------+---------+---------+------------+
8 rows in set (0.00 sec)
 
统计各科成绩各分数段人数:课程编号,课程名称,[100-85] [85-70] [70-60] [60-0] 及所占百分比
select c.cid as '课程编号', c.cname as '课程名称', A.*
from course as c,
(select cid,
    sum(case when score >= 85 then 1 else 0 end)/count(*) as 100_85,
    sum(case when score >= 70 and score < 85 then 1 else 0 end)/count(*) as 85_70,
    sum(case when score >= 60 and score < 70 then 1 else 0 end)/count(*) as 70_60,
    sum(case when score < 60 then 1 else 0 end)/count(*) as 60_0
from sc group by cid) as A
where c.cid = A.cid;
 
+------+------+------+--------+--------+--------+--------+
|      |      | cid  | 100_85 | 85_70  | 70_60  | 60_0   |
+------+------+------+--------+--------+--------+--------+
| 01   | ??   | 01   | 0.0000 | 0.6667 | 0.0000 | 0.3333 |
| 02   | ??   | 02   | 0.5000 | 0.1667 | 0.1667 | 0.1667 |
| 03   | ??   | 03   | 0.3333 | 0.3333 | 0.0000 | 0.3333 |
+------+------+------+--------+--------+--------+--------+
3 rows in set (0.00 sec)
 
查询各科成绩前三名的记录
select * from (select *, rank() over(partition by cid order by score desc) as graderank from sc) A
where A.graderank <= 3;
 
+------+------+-------+-----------+
| sid  | cid  | score | graderank |
+------+------+-------+-----------+
| 01   | 01   |  80.0 |         1 |
| 03   | 01   |  80.0 |         1 |
| 05   | 01   |  76.0 |         3 |
| 01   | 02   |  90.0 |         1 |
| 07   | 02   |  89.0 |         2 |
| 05   | 02   |  87.0 |         3 |
| 01   | 03   |  99.0 |         1 |
| 07   | 03   |  98.0 |         2 |
| 02   | 03   |  80.0 |         3 |
| 03   | 03   |  80.0 |         3 |
+------+------+-------+-----------+
10 rows in set (0.00 sec)
 
 
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值