1.概述
在应用系统开发过程中,由于初期数据量小,开发人员写sql语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多sql语句开始逐渐显露出性能问题,对生产环境的影响也越来越大,此时这些有问题的sql语句就成为整个系统性能的瓶颈,因此我们必须要对它们进行优化。
2.通过show status命令了解各种sql的执行频率
mysql客户端连接成功后,通过show [session|global]status命令可以提供状态信息,也可以在操作系统上使用mysqladmin extended-status命令获得这些消息。show [session|global] status可以根据需要加上参数“session”或者“global”来显示session级(当前连接)的统计结果和global级(自数据库上次启动至今)的统计结果。如果不写,默认使用参数是“session”。
下面的命令显示了当前session中所有统计参数的值:
-- 查看会话所有统计的值 show status like 'com_%'; or show session status like 'com_%';
下面的命令显示了当前global中所有统计参数的值:
-- 查看全局所有统计的值
show global status like 'com_%';
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:慢查询的次数。
3.定位执行效率较低的sql语句
可以通过以下两种方式定位执行效率较低的sql语句。
-
通过慢查询日志定位那些执行效率较低的sql语句,用--log-slow-queries[=file_name]选项启动时,mysqld写一个包含所有执行时间超过long_query_time秒的sql语句的日志文件。
-
慢查询日志在查询结束以后才纪录,所以在应用系统反映执行效率出现问题的时候查询慢查询日志并不能定位问题,可以使用show processlist命令查看当前mysql在进行的线程,包括线程的状态、是否锁表等,可以实时地查看sql的执行情况,同时对一些锁表操作进行优化。
4.通过explain分析低效sql的执行计划
通过定位执行效率较低的sql语句后,可以通过explain或者desc命令获取mysql如何执行select语句的信息,包括在select语句执行过程中表如何连接和连接的顺序,比如想统计所有库存阶梯数量,需要关联goods_stock表和goods_stock_price表,并且对goods_stock_price.qty字段做求和(sum)操作,相应 sql 的执行计划如下:
explain select sum(sp.qty) from goods_stock as s left join goods_stock_price as sp on s.id=sp.goodsstockid;
如上图所示每个列的简单解释如下:
-
select_type:表示 select 的类型,常见的取值有:
-
simple(简单表,即不使用表连接 或者子查询)。
-
primary(主查询,即外层的查询)、union(union 中的第二个或 者后面的查询语句)、◎subquery(子查询中的第一个select)等。
-
-
table:输出结果集的表。
-
type:表示表的连接类型,性能由好到差的连接类型为:
-
system(表中仅有一行,即常量表)。
-
const(单表中最多有一个匹配行,例如primary key或者unique index)。
-
eq_ref(对于前面的每一行,在此表中只查询一条记录,简单来说,就是多表连接中使用primary key或者unique index)。
-
ref(与eq_ref类似,区别在于不是使用primary key或者unique index,而是使用普通的索引)。
-
ref_or_null(与ref类似,区别在于条件中包含对null的查询)。
-
index_merge(索引合并优化)。
-
unique_subquery(in的后面是一个查询主键字段的子查询)。
-
index_subquery(与unique_subquery类似,区别在于in的后面是查询非唯一索引字段的子查询)。
-
range(单表中的范围查询)。
-
index(对于前面的每一行,都通过查询索引来得到数据)。
-
all(对于前面的每一行,都通过全表扫描来得到数据)。
-
-
possible_keys:表示查询时,可能使用的索引。
-
key:表示实际使用的索引。
-
key_len:索引字段的长度。
-
rows:扫描行的数量。
-
filtered:返回结果的行占需要读到的行(rows列的值)的百分比。
-
extra:执行情况的说明和描述。
-
using index(此值表示mysql将使用覆盖索引,以避免访问表)。
-
using where(mysql 将在存储引擎检索行后再进行过滤,许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带where子句的查询都会显示“using where”。“using where”有时提示了一种可能性:查询可以从不同的索引中受益。
-
using temporary(mysql 对查询结果排序时会使用临时表)。
-
mysql will apply an external index sorting on the results instead of reading rows from the table in index order.。mysql有两种文件排序算法,这两种排序方式都可以在内存或者磁盘上完成,explain不会告诉你mysql将使用哪一种文件排序,也不会告诉你排序会在内存里还是磁盘上完成)。
-
range checked for each record(index map: n) (没有好用的索引,新的索引将在联接的每一行上重新估算,n是显示在possible_keys列中索引的位图,并且是冗余的)。
-
5.确定问题并采取相应的优化措施
经过以上定位步骤,我们基本就可以分析到问题出现的原因。此时我们可以根据情况采取相应的改进措施,进行优化提高语句执行效率。
在上面的例子中,已经可以确认是goods_stock是走主键索引的,但是对goods_stock_price子表的进行了全表扫描导致效率的不理想,那么应该对goods_stock_price表的goodsstockid字段创建索引,具体命令如下:
-- 创建索引 create index idx_stock_price_1 on goods_stock_price (goodsstockid); -- 附加删除跟查询索引语句 alter table goods_stock_price drop index idx_stock_price_1; show index from goods_stock_price;
创建索引后,我们再看一下这条语句的执行计划,具体如下:
explain select sum(sp.qty) from goods_stock as s left join goods_stock_price as sp on s.id=sp.goodsstockid;
可以发现建立索引后对goods_stock_price子表需要扫描的行数明显减少(从 3 行减少到1行),可见索引的使用可以大大提高数据库的访问速度,尤其在表很庞大的时候这种优势更为明显。