前言

在应用的开发过程中,由于初期数据量小,开发人员写SQL语句时更重视功能上的实现。但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句逐渐显露出性能问题,对生产的影响也越来越大,此时这些有问题的SQL语句就成为了整个系统性能的瓶颈。因此我们必须要对它们进行优化。

优化SQL语句的一般步骤

通过show status命令了解各种SQL的执行频率

MySQL客户端连接成功后,通过show [session | global] status命令可以查看服务器状态信息。
session表示当前连接的统计结果,是默认值。global表示自数据库上次启动至今的统计结果。

  1. mysql> show status like 'Com_%';
  2. +---------------------------+-------+
  3. | Variable_name | Value |
  4. +---------------------------+-------+
  5. | Com_admin_commands | 0 |
  6. | Com_alter_event | 0 |
  7. | Com_alter_table | 0 |
  8. | Com_analyze | 0 |
  9. | Com_begin | 0 |
  10. | Com_commit | 0 |
  11. | Com_create_db | 0 |
  12. | Com_delete | 0 |
  13. | Com_empty_query | 0 |
  14. | Com_execute_sql | 0 |
  15. | Com_flush | 0 |
  16. | Com_grant | 0 |
  17. | Com_insert | 0 |
  18. | Com_lock_tables | 0 |
  19. | Com_prepare_sql | 0 |
  20. | Com_select | 1 |
  21. | Com_update | 0 |

Com_xxx表示每一各xxx语句执行的次数,我们通常比较关心的是以下几个统计参数:

  • Com_select:执行select操作的次数,一次查询累加1。

  • Com_insert:执行insert操作的次数,对于批量插入的insert操作,只累加一次。

  • Com_update:执行update操作的次数。

  • Com_delete:执行DELETE操作的次数。

以上参数对于所有存储引擎的表操作都会进行累计。下面这几个参数只是针对InnoDB存储引擎的,累加的算法也略有不同。

  • Innodb_rows_read:执行select查询返回的行数。

  • Innodb_rows_inserted:执行insert操作插入的行数。

  • Innodb_rows_updated:执行update操作更新的行数。

  • Innodb_rows_deleted:执行delete操作删除的行数。

通过以上几个参数,可以很容易地了解当前数据库的应用是以插入更新为主还是以查询操作为主,以及各种类型的SQL大致的执行比例是多少。其中更新操作的计数,是对执行次数的计数,不论提交还是回滚都会进行累加。

对于事务型的应用,通过Com_commit和Com_rollback可以了解事务提交和回滚的情况,对于回滚操作非常频繁的数据库,可能意味着应用编写存在问题。

此外,以下几个参数便于用户了解数据库的基本情况:

  • Connections:试图连接MySQL服务器的次数。

  • Uptime:服务器工作时间。

  • Slow_queries:慢查询的次数。

定位执行效率较低的SQL语句

  • 慢查询日志。

  • show processlist命令。查看当前MySQL在进行的线程,包括线程状态、是否锁表等,可以实时地查看SQL的执行情况,同时对一些锁表操作进行优化。

EXPLAIN分析低效SQL的执行计划

通过以上步骤查询到效率低的SQL语句后,可以使用EXPLAIN命令或者DESC命令获取MySQL如何执行SQL语句的信息。EXPLAIN为每一个被用于查询的表返回一行信息,它按照MySQL在处理语句时读取它们的顺序被列出。主要返回列说明:

id 执行序号
table 表名
type 表的连接类型。性能由好到差依次为:system、const、eq_ref、ref、fulltext、ref_or_null、index_merge、unique_subquery、index_subquery、range、index、ALL
possible_keys 可能使用的索引。
key 实际使用的索引
key_len 索引字段的长度。
rows 扫描行的数量。

确定问题并采取相应的优化措施

经过以上步骤,基本就可以确认问题出现的原因。此时用户可以根据情况采取相应的措施,进行优化提高执行的效率。

建立索引

索引时数据库优化中最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数的SQL性能问题。

索引的存储分类

MyISAM存储引擎的表的数据和索引是自动分开存储的,各自是独立的一个文件;
InnoDB存储引擎的表的数据和索引是存储在同一个表空间里面,但可以由多个文件组成。
MySQL中索引的存储类型目前只有两种:BTREE和HASH。

MySQL如何使用索引

索引用于快速找出在某个列中有一特定值的行。对相关列使用索引是提高SELECT操作性能的最佳途径。
查询要使用索引最主要的条件是查询条件中需要使用索引关键字,如果是多列索引,那么只有查询条件使用了多列关键字最左边的前缀时,才可以使用索引,否则将不能使用索引。不会使用索引的情况有:

  • 如果MySQL估计使用索引比全表扫描更慢,则不使用索引。

  • 使用or关键字,即使两边的条件中都加了索引,查询时也不会使用索引。

  • 使用like模糊查询时%放在第一位。

查看索引使用情况

使用命令:show status like ‘Handler_read%’;
如果索引正在工作,Handler_read_key的值将很高,这个值代表了一个行被索引值读的次数,很低的值表明增加索引得到的性能改善不高,因为索引并经经常使用。Handler_read_rnd_next的值高则意味着查询运行低效,并且应该建立索引补救。这个值的含义是在数据文件中读下一行的请求数。值高表示正进行大量的表扫描。

常用SQL的优化

前面我们介绍了MySQL中怎么样通过索引来优化查询。日常开发中,除了使用查询外,我们还会使用一些其他的常用SQL,比如INSERT、GROUP BY等。针对这些SQL,也有一些最佳实践。

  • group by默认会对字段排序。如果查询包含group by,但想要避免排序结果的消耗,可以指定order by null禁止排序。

  • 在某些情况下,MySQL可以使用一个索引来满足order by子句,而不需要额外的排序。where条件和order by使用相同的索引,并且order by的顺序和索引顺序相同,并且order by的字段都是升序或者都是降序。

  • 使用join很多时候比子查询要高效。

小结

SQL优化问题是数据库性能优化最基础也是最重要的一部分,实践表明很多数据库性能问题都是由不合适的SQL语句造成。通过本文,可以大致了解到SQL优化的一般步骤,即定位问题,分析原因,解决问题。

参考资料:《深入浅出MySQL》