Mysql慢查询分析及优化

什么是慢查询?

慢查询,顾名思义,执行很慢的查询。当执行SQL超过long_query_time参数设定的时间阈值(默认10s)时,就被认为是慢查询,这个SQL语句就是需要优化的。慢查询被记录在慢查询日志里

慢查询日志默认是不开启的。如果需要优化SQL语句,就可以开启这个功能,它可以让你很容易地知道哪些语句是需要优化的。

慢查询配置

以MySQL数据库为例,默认慢查询功能是关闭的,当慢查询开关打开后,并且执行的SQL语句达到参数设定的阈值后,就会触发慢查询功能打印出日志。

  • 慢查询日志

    查询是否开启慢查询日志:
    show variables like ‘slow_query_log’;
    
    开启慢查询sql:
    set global slow_query_log = 1/on;
    
    关闭慢查询sql:
    set global slow_query_log = 0/off;
    
  • 未使用索引是否开启日志

    查询未使用索引是否开启记录慢查询日志: 
    show variables like ‘log_queries_not_using_indexes’;
    
    开启记录未使用索引sql:
    set global log_queries_not_using_indexes=1/on
    
    关闭记录未使用索引sql:
    set global log_queries_not_using_indexes=0/off
    
  • 慢查询时间设置

    查询超过多少秒的记录到慢查询日志中:
    show variables like ‘long_query_time’;
    
    设置超X秒就记录慢查询sql:
    set global long_query_time= X;
    
  • 上述这些参数设置都是在当前数据库生效,当MySQL重启后则会失效。如果要永久生效,就必须修改配置文件my.cnf

  • 慢查询路径

    查询MySQL慢查询日志的路径:
    show variables like ‘slow_query_log_file%’;
    
慢查询日志分析:mysqldumpslow工具

以MySQL为例,一般使用mysqldumpslow工具分析慢查询日志,使用命令查询慢SQL语句。

查询用时最多的10条慢:

sql mysqldumpslow -s t -t 10 -g 'select' /data/mysql/data/dcbi-3306/log/slow.log

得到其中一条如下图所示的结果:

  • Count:代表这个 SQL 语句执行了多少次
  • Time:代表执行的时间,括号是累计时间
  • Lock:表示锁定的时间,括号是累计时间
  • Rows:表示返回的记录数,括号是累计记录数

有了这样清晰的慢查询日志分析之后,我们可以更加有针对性和更快捷的处理出现慢查询SQL语句的问题,直接找到对应程序位置优化代码从而避免慢查询出现。

exists、not exists和in、not in
  • mysql中的in语句是把外表和内表作hash 连接,in查询的子条件返回结果必须只有一个字段

  • 而exists语句是对外表作loop循环,每次loop循环再对内表进行查询。

  • 一直大家都认为exists比in语句的效率要高,这种说法其实是不准确的。这个是要区分环境的。

    • 如果查询的两个表大小相当,那么用in和exists差别不大。

    • 如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in:

select *from small where title in (select title from big);

效率不如

select *from small where exists (select title from big where title=small.title);
not in 和not exists
  • 如果查询语句使用了not in 那么内外表都进行全表扫描,没有用到索引;
  • 而not extsts 的子查询依然能用到表上的索引。所以无论那个表大,用not exists都比not in要快
  • 可以知道not in是个范围查询,这种!=的范围查询无法使用任何索引,等于说A表的每条记录,都要在B表里遍历一次,查看B表里是否存在这条记录。
in 和 exists
  • 括号里面查询的表大时,exists效率高
  • 括号里面查询的表小时,in效率高
索引失效

之所以会出现慢查询,无疑是SQL语句的问题,一般都是扫描数据量过大没有使用索引索引失效等导致。如下是一些索引失效的情况:

  • 使用LIKE关键字的查询语句

    • 在使用LIKE关键字进行查询的查询语句中,如果匹配字符串的第一个字符为“%”,索引不会起作用。只有“%”不在第一个位置索引才会起作用。
  • 使用多列索引的查询语句

    • MySQL可以为多个字段创建索引。一个索引最多可以包括16个字段。对于多列索引,只有查询条件使用了这些字段中的第一个字段时,索引才会被使用,也就是左匹配原则

    • 最左匹配原则:最左优先,以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。

      • 例如:b = 2 如果建立(a,b)顺序的索引,是匹配不到(a,b)索引的;
      • 但是如果查询条件是a = 1 and b = 2或者(又或者是b = 2 and a = 1)就可以,因为优化器会自动调整a,b的顺序。
      • 再比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,因为c字段是一个范围查询,它之后的字段会停止匹配。
SQL语句优化
  • 查询语句应该尽量避免全表扫描,首先应该考虑在Where子句以及OrderBy子句上建立索引,但是每一条SQL语句最多只会走一条索引,

    • 而建立过多的索引会带来插入和更新时的开销,同时对于区分度不大的字段,应该尽量避免建立索引
    • 可以在查询语句前使用explain关键字,查看SQL语句的执行计划,判断该查询语句是否使用了索引;
  • 应尽量使用EXIST和NOT EXIST代替 IN和NOT IN,因为后者很有可能导致全表扫描放弃使用索引;

  • 应尽量避免在Where子句中对字段

    • 进行NULL判断,因为NULL判断会导致全表扫描;
    • 使用or作为连接条件,因为同样会导致全表扫描;
    • 使用!=或者<>操作符,同样会导致全表扫描;
    • 使用like “%abc%” 或者like “%abc” 同样也会导致全表扫描,而like “abc%”会使用索引。
    • 使用表达式操作符,因为会导致全表扫描;
    • 对字段使用函数,因为同样会导致全表扫描
  • 在使用Union操作符时,应该考虑是否可以使用Union ALL来代替,因为Union操作符在进行结果合并时,会对产生的结果进行排序运算,删除重复记录,对于没有该需求的应用应使用Union ALL,后者仅仅只是将结果合并返回,能大幅度提高性能;

  • Select语句中尽量 避免使用“*”,因为在SQL语句在解析的过程中,会将“”转换成所有列的列名,而这个工作是通过查询数据字典完成的,有一定的开销;

  • Where子句中,表连接条件应该写在其他条件之前,因为Where子句的解析是从后向前的,所以尽量把能够过滤到多数记录的限制条件放在Where子句的末尾;

  • 若数据库表上存在诸如index(a,b,c)之类的联合索引,则Where子句中条件字段的出现顺序应该与索引字段的出现顺序一致,否则将无法使用该联合索引;

  • From子句中表的出现顺序同样会对SQL语句的执行性能造成影响,From子句在解析时是从后向前的,即写在末尾的表将被优先处理,应该选择记录较少的表作为基表放在后面,同时如果出现3个及3个以上的表连接查询时,应该将交叉表作为基表;

  • 尽量使用>=操作符代替>操作符

    • 例如,如下SQL语句

      select dbInstanceIdentifier from DBInstance where id > 3;
      
    • 该语句应该替换成

      select dbInstanceIdentifier from DBInstance where id >=4 
      
    • 两个语句的执行结果是一样的,但是性能却不同,后者更加 高效

    • 因为前者在执行时,首先会去找等于3的记录,然后向前扫描,而后者直接定位到等于4的记录。

表结构优化

这里主要指如何正确的建立索引,因为不合理的索引会导致查询全表扫描,同时过多的索引会带来插入和更新的性能开销;

  • 首先要明确每一条SQL语句最多只可能使用一个索引,如果出现多个可以使用的索引,系统会根据执行代价,选择一个索引执行;

  • 对于Innodb表,虽然如果用户不指定主键,系统会自动生成一个主键列,但是自动产生的主键列有多个问题

    • 性能不足,无法使用cache读取;
    • 并发不足,系统所有无主键表,共用一个全局的Auto_Increment列。
    • 因此,InnoDB的所有表,在建表同时必须指定主键
  • 对于区分度不大的字段,不要建立索引;

  • 一个字段只需建一种索引即可,无需建立了唯一索引,又建立INDEX索引。

  • 对于大的文本字段或者BLOB字段,不要建立索引;

  • 连接查询的连接字段应该建立索引;

  • 排序字段一般要建立索引;

  • 分组统计字段一般要建立索引;

  • 正确使用联合索引,联合索引的第一个字段是可以被单独使用的

    • 例如有如下联合索引:

      index(userID,dbInstanceID)
      
    • 查询语句是可以使用该索引的

      select dbInstanceIdentifier from DBInstance where userID=? 
      
    • 但是下面语句就不可以使用该索引

      select dbInstanceIdentifier from DBInstance where dbInstanceID=?
      
  • 索引一般用于记录比较多的表,假如有表DBInstance,所有查询都有userID条件字段,目前已知该字段已经能够很好的区分记录,即每一个userID下记录数量不多,所以该表只需在userID上建立一个索引即可

    • 即使有使用其他条件字段,由于每一个userID对应的记录数据不多,所以其他字段不用索引基本无影响
    • 同时也可以避免建立过多的索引带来的插入和更新的性能开销;

猜你喜欢

转载自blog.csdn.net/s_frozen/article/details/129031350