• MySQL——查询性能优化


    一、为什么查询速度会慢

    通常来说:查询生命周期大致可以按顺序来看:从客户端,到服务器,然后在服务器上进行解析,生成执行计划、执行,并返回结果给客户端

    执行可以认为是整个生命周期中最重要的阶段,包括大量的检索数据到存储引擎的调用以及调用后的数据处理(排序和分组等)。

    二、慢查询基础:优化数据访问

    查询性能低下最基本的原因是访问的数据太多。

    ① 确认应用程序是否在搜索大量超过需要的数据。

    ② 确认MySQL服务器层是否在分析大量超过需要的数据行。

    1、是否向数据库请求了不需要的数据

    ① 查询不需要的记录

    -- 若页面上只有需要10行数据  返回100行甚至更多是得不偿失的 最简单方法  limit
    select <cols> from table where col_name = 'col_val'  limit 10;

    常见的错误认识:MySQL只返回需要的结果集,实际上MySQL是先返回全部结果集再进行运算的,

    -- 以前公司分页框架 查询count时
    select count(*) fromselect <cols> from table where ...)
    -- 这个时候MySQL会查询出全部<cols>然后进行count处理,查询效率很低,可优化为
    select count(*) from table where ...

    ② 总是取出全部的列+多表关联时返回全部的列

    查询中应该拒绝使用 '*'

    select * from table where ...
    -- * 会查询所有的列,平常学习或者解决问题时用*没有问题,但不要在生产应用中使用*
    -- 特别是多表关联时  会查询出所有的表的所有列 数据量巨大

    ③ 避免重复查询相同的数据

    热点数据,最好使用缓存解决。避免重复不断地查询相同的数据。

    2、MySQL是否扫描了额外的记录

    对于MySQL,最简单的衡量查询的指标:响应时间、扫描的行数、返回的行数。

    ① 响应时间

    在日常开发中,需要重视响应时间。一般公司都会周期统计响应时间过长的SQL。响应时间=服务时间+排队时间。

    ② 扫描的行数与返回的行数

    分析查询时:查看该查询扫描的行数是非常有帮助的。这在一定程度上能够说明该查询找到需要的数据的效率高不高。理想情况下扫描的行数=返回的行数,但实际操作过程中多表联合查询出一行数据是常态。

    -- 注意explain sql 返回列表中的rows属性:扫描的行数

    ③ 扫描的行数和访问类型

    -- explain sql 返回列表中的type属性:访问类型
    -- 速度从慢到快 扫描的行数也是从小到大
    -- all :全表扫描
    -- index :索引扫描
    -- range :范围扫描 一般要求最少要达到range
    -- ref :非唯一索引扫描
    -- eq_ref:唯一索引扫描
    -- cons :常熟引用

    ④ 一般MySQL能够使用三种方式应用where条件(extra : using where),从好到坏依次是:

    a 在索引中使用where条件过滤不匹配的记录。这是在MySQL存储引擎层完成的

    b 使用索引覆盖(覆盖索引)扫描来返回记录(extra :using index),直接从索引中过滤不需要的记录并返回命中的结果。这是由MySQL服务器层完成的。

    c 从数据表中返回数据,然后过滤不满足条件的记录(extra : using where)。这也是在MySQL服务器层完成的。

    select actor_id,count(1) from sakila.film_actor group by actor_id;
    -- 误区:应用where条件不是sql中的where
    -- 当actor_id是二级索引 对应a
    -- 当actor_id是主键索引(聚簇)对应b
    -- 当actor_id不是索引    对应c

    ⑤ 查询需要扫描大量的数据只返回少量的行,通常有下列技巧优化

    a 使用索引覆盖扫描,把所有需要返回的列都放到索引中,存储引擎无须回表获取对应的行就可以返回数据了。

    b 改变库表结构。使用单独的汇总表

    c 重写这个复杂的查询

    三、重构查询的方式

     1、一个复杂查询还是多个简单查询

    设计查询的一个重要问题:是否需要将一个复杂的查询分为多个简单的查询

    传统实现中:总是强调需要数据层完成尽可能多的工作,这样做的逻辑在以前总是认为网络通信、查询解析和优化是一件代价很高的事情。

    MySQL:MySQL从设计上让连接和断开连接都很轻量级,在返回一个小的查询结果方面很高效。并且现在网络速度比以前要快的多。

    实际设计中一个大查询分解成多个小查询是很有必要的。不要害怕这样做,但也要衡量一下开发成本,这样是不是会减少工作量。例如:查询一个很小的表,完全没必要拆分开来。

    2、切分查询

    有时候对于一个大查询我们需要“分而治之”,将大查询切分为小查询,每个查询功能完全一样,只完成一小部分,每次只返回一小部分查询结果

    例如:删除旧数据。定期地清理大量数据时,如果用一个大的语句一次性完成的话,可能需要一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。

    这个时候我们可以将delete语句切分为多个较小的查询,尽可能小的影响MySQL的性能。

    delete from messages where created < date_sub(now(),interval 3 month);
    -- 可分治为下面逻辑
    delete from messages where created < date_sub(now(),interval 3 month) limit 10000;
    -- 然后自定义执行时间,多执行几次,例如一个小时一次,一天就是24w数据

    3、分解关联查询

    很多高性能的应用都会对关联查询进行分解。例如:

    select * from tag
    join tag_post on tag_post.tage_id=tag.id
    join post on tag_host.post_id=post.id
    where tag.tag='mysql';
    -- 可以分解为
    select * from tag where tag='mysql';
    select * from tag_post where tag_id= 1234;
    select * from post where post.id in (123,456,567,9098,8904);

    这样重构的优点:

    ① 让缓存的效率更高,缓存多张表的数据。

    ② 将查询分解后,执行单个查询可以减少锁的竞争

    ③ 在应用层做关联,可以更容易对数据库进行拆分,更容易做到高性能和可扩展。

    ④ 查询本身效率也可能有所提升。例如控制in()中id顺序查询比随机的关联要更高效。

    ⑤ 可以减少冗余记录的查询。

    ⑥ 更进一步,这样做相当于在应用中实现了hash关联,而不是使用MySQL的嵌套循环关联。

    四、查询执行的基础

    MySQL查询过程:

     1、客户端/服务器通信协议

    ① “半双工”通信协议:让MySQL通信简单快速,但有一个明显的限制无法进行流量的控制。

    ② 对于一个MySQL连接或者说一个线程,任何时候都有一个状态,这个状态表示MySQL当前正在做什么。

    -- 由于“半双工”通信协议。了解这些状态含义,可以很快了解处理进度。
    -- Sleep  :线程正在等待客户端发送新的请求
    -- Query :线程正在执行查询或者正在将结果发送给客户端
    -- Locked :在MySQL服务器层,该线程正在等待表锁。
    -- Analyzing and statistics :线程正在收集存储引擎的统计信息,并生成查询执行计划
    -- Coping to tmp table [on disk] :线程正在执行查询,并且将其结果集都复制到一个临时表中,这种状态一般要么是在做group by要么order by 或者是 union操作。
    -- Sorting result :线程正在对结果集进行排序。
    -- Sending data :这表示多种情况:线程可能在多个状态之间传送数据;生成结果集;在向客户端返回数据

    2、查询缓存

    在解析一个查询语句之前,如果查询缓存时打开的,MySQL会优先检查这个查询是否命中查询缓存中的数据。

    ① 这个检查是通过对一个大小写敏感的哈希查找实现的,查询和缓存查询即时一个字节不同,也视作为未命中

    ② 查询命中缓存后,MySQL还会检查用户权限。

    3、查询优化处理

    ① 语法解析器和预处理:MySQL通过关键字将SQL语句进行解析,并生成一棵对应的“解析树”,并且对解析树进行语法规则检查。

    一个简单的语法树模型,MySQL中肯定会更复杂

    ② 查询优化器 :当语法树被认为是合法的,优化器会从很多种执行方式,找到最好的执行计划。

    select sql_no_cache count(*) from sakila.film_actor;
    -- 5462
    show status like 'last_query_cost'
    -- 1040.599000
    -- 结果显示MySQL优化器认为大概需要做1040个数据页的随机查找才能完成上面的查询。

    一下原因可能会导致优化器选择错误的执行计划:

    a 统计信息不准确。

    b 执行计划中的成本估算不等同于实际执行的成本

    c MySQL的最优可能和你想的最优不一样。时间短 != 执行成本

    d MySQL不考虑其他并发执行的查询

    e MySQL不是任何时候都是基于成本的优化。又是也会基于一些固定的规则。

    f MySQL不会考虑不受其控制的操作的成本

    g 优化器有时无法估算所有可能的执行计划,而错过实际最优执行计划

    MySQL能够处理的优化类型:

    重新定义关联表的顺序:数据表关联并不总是按照查询中指定顺序进行的。

    将外连接转化成内连接:并不是所有outer join语句都必须以外连接方式执行。例如where条件、库表结构可能使外连接等价于一个内连接。

    使用等价变换规则:MySQL可以使用一些等价变化来简化规范表达式。(5 = 5 and a > 5)改写为 a > 5

    优化count()min()和max():索引和列是否可为空通常可以帮助MySQL优化这类表达式。

    预估并转化为常数表达式:当MySQL检测到一个表达式可以转化为常数的时候。就会一直把该表达式作为常数进行优化处理

    覆盖索引扫描:当索引中的列包含所有查询中需要使用的列的时候,MySQL就可以使用索引返回需要的数据。

    子查询优化:MySQL在某些情况下可以将子查询转换一种效率更高的形式。

    提前终止查询:在发现以满足查询需求的时候,MySQL总是能够立刻终止查询。limit

    等值传播:如果两个列的值通过等式关联,那么MySQL能够把其中一个列的where条件传到另一列上。

    列表IN()的比较:在有些数据库系统中in()等同于or,但MySQL不是,in()列表中数据先排序,然后二分查找方式确定列表中值是否满足条件。or=O(n)则in=O(logn)

    ③ 数据和索引的统计信息

    在服务层有查询优化器,却没有保存数据和索引的统计信息。统计信息由存储引擎实现。

    ④ MySQL如何执行关联查询

    MySQL对任何关联都执行嵌套循环关联操作,MySQL先在一个表中循环取出单条数据,然后嵌套循环到下一个表中寻找匹配的行,依次直至执行完毕

    ⑤ 执行计划

    与很多其他关系数据库不同,MySQL并不会生成查询字节码来执行查询,生成查询的一棵指令树。

    ⑥ 关联查询优化器

    MySQL优化器最重要的一部分就是关联查询优化,它决定了多个表关联时的顺序。

    ⑦ 排序优化

    无论如何排序都是一个成本很高的操作,所以从性能角度考虑,应尽可能避免排序或者尽可能避免对大量数据进行排序。

    4、查询执行引擎

    在解析和优化阶段,MySQL将生成查询对应的执行计划,MySQL的查询执行引擎则根据这个执行计划完成整个查询。

    5、MySQL查询优化器的局限性

    ① 关联子查询:MySQL的子查询实现得非常糟糕。最糟糕的一类查询时where条件中包含in()的子查询语句。

    select * from sakila.film where film_id in (
    select film_id from sakila.film_actor where actor_id = 1);
    -- 我们通常以为会这样执行
    select * from sakila.film where film_id in (1,23,25,106...);
    --  实际并非如此
    select * from sakila.film where exists (
    select * from sakila.film_actor where actor_id = 1
    and film_actor.film_id = film.film_id);
    -- 很容易重写这个查询
    select film.* from sakila.film
    inner join sakila.film_actor using(film_id)
    where actor_id = 1;

    ② UNION的限制

    ③ 索引合并优化

    ④ 等值传递

    ⑤ 并行执行

    ⑥ 哈希关联

    ⑦ 松散索引扫描

    ⑧ 最大值和最小值优化

    ⑨ 在同一个表上查询和更新

    五、优化特定类型的查询

     一、优化count()查询

    count()是一个特殊的函数,有两种非常不同的作用:

    统计某个列值的数量:在count()的括号中指定了列或者列的表达式,则统计的就是这个表达式有值(不为NULL)的结果数。

    统计行数:count(*)中*并不会像我们猜想的那样扩展成所有的列count(*) = count(1)在统计行数时最好使用count(*),这样写意义清晰,性能也很好。

    select count(*) from world.city where id > 5;
    -- 反转一下
    select (select count(*) from worldl.city) - count(*) from world.city where id <= 5;
    
    select sum(if(color = 'blue',1,0)) as blue,sum(if(color = 'red',1,0)) as red from items;
    select count(color = 'blue' or null) as blue,count(color = 'red' or null) as red from items;

    二、优化关联查询

    ① 确保on 或者using子句中的列上索引。

    ② 确保任何group by和order by中表达式只涉及到一个表中的列,这样MySQL才有可能使用索引来优化这个过程

    ③ 当升级MySQL的时候需要注意:关联语法、运算符优先级等其他可能会发生变化的地方。

    三、优化子查询

    ① 尽可能的使用关联查询替代子查询。

    四、优化group by和distinct

    ① 索引优化是最有效的方法。

    ② 无法使用索引时,group by使用两种策略来完成:使用临时表或者文件排序来做分组。

    select actor.first_name, actor.last_name,count(*) from sakila.film_actor
        inner join sakila.actor using(actor_id) group by actor.first_name, actor.last_name;
    -- 采用查找表的表示列分组的效率会比其他列更高
    select actor.first_name, actor.last_name,count(*) from sakila.film_actor
        inner join sakila.actor using(actor_id) group by film_actor.actor_id;

    五、优化limit分页

    当偏移量很大时,例如limit 10000,20 查询出10020条记录后只返回20条。代价非常高。

    可以使用索引覆盖扫描,而不是查询所有的列。

    select film_id,description from sakila.film order by title limit 50,5;
    -- 延迟关联将大大提高查询效率
    select film.film_id,film.description from sakila.film
        inner join (
            select film_id from sakila.film order by title limit 50,5
        ) as lim using(film_id);
    -- 或者根据返回film_id值
    select film_id,description from sakila.film where film_id > ? order by title limit 5;

    六、优化sql_calc_found_rows

    分页的时候,一个更好的技巧是

    ① 具体页数换成下一页按钮,每页显示20条记录,那么可以每次查询21条记录,仅显示20条,第21条数据用作下一页按钮的判断

    ② 先获取并缓存较多的数据,例如缓存1000条数据,每次分页从缓存中取数据,如果结果集大于1000,则可以页面上设计一个额外的“找到的结果多于1000条”

    七、优化union查询

    MySQL总是通过创建并填充临时表的方式来执行union查询。因此很多优化策略在union查询中都没法很好地使用。

    除非确实需要服务器消除重复的行,否则一定要使用union all。

    八、静态查询分析

    九、用户自定义变量

    参考《高性能MySQL》

  • 相关阅读:
    Python pandas 入门 05 JSON
    Python matplotlib 画图入门 07 散点图
    Python matplotlib 画图入门 06 绘制多图
    Python pandas 入门 04 CSV 文件
    Python pandas 入门 01 安装
    Python 入门示例系列 35 迭代器与生成器
    Python matplotlib 画图入门 03 绘图线
    Python pandas 入门 目录
    Python 零散知识点琐碎知识
    Python numpy 入门系列 目录
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12359873.html
Copyright © 2020-2023  润新知