当面对一个有 SQL性能问题的数据库时,我们应该从何处入手来进行系统的分析,使得能够尽快定位问题 SQL 并尽快解决问题 。
优化sql语句的一般步骤
一、通过 show status 命令了解各种SQL的执行频率
通过 show [session | global] status 命令可以提供服务器状态信息,也可以在操作系统上使用 mysqladmin extended-status 命令获取这些消息。
show [session|global] status 可以根据需要加上参数“session”或者“global”来显示 session 级(当 前连接)的统计结果和 global 级(自数据库上次启动至今)的统计结果。如果不写,默认使 用参数是“session”。
显示session中所有的统计参数值:
mysql> show status like ‘Com_%‘;
+---------------------------+-------+
| Variable_name | Value |
+---------------------------+-------+
| Com_admin_commands | 0 |
| Com_assign_to_keycache | 0 |
| Com_alter_db | 0 |
| Com_alter_db_upgrade | 0 |
| Com_alter_event | 0 |
| Com_alter_function | 0 |
| Com_alter_procedure | 0 |
| Com_alter_server | 0 |
| Com_alter_table | 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 操作删除的行数。
通过以上几个参数我们可以了解到?
(1)当前数据库的应用是以插入更新为主还是以查询操作为主
(2) 各种类型的sql大致执行的比例是多少。对于更新操作的计数,是对执行 次数的计数,不论提交还是回滚都会进行累加。
对于事务型的应用
* Com_commit 事务提交
* Com_rollback 事务回滚
对于回滚操作非常频繁的数据库,可能意味着应用编写存在问题。
对于了解数据库的基本情况
* Connections :试图连接mysql 服务器的次数。
* uptime :服务器工作时间。
* slow_queries :慢查询的次数。
二、定位执行效率低的sql语句
(1)通过慢查询日志定位执行效率低的sql语句
(2)使用 show processlist 命令查看当前 MySQL 在进行的线程
慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题,可以使用 show processlist 命令查看当前 MySQL 在进行的线程, 包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况,同时对一些锁表操 作进行优化。
三、通过 explain 分析低效 sql 的执行计划
执行一条sql:
mysql> explain select sum(age) from sales a,company b where a.company_id = b.id and age = 20\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: a
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 65913
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: b
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: test1.a.company_id
rows: 1
Extra: Using index
2 rows in set (0.00 sec)
每个列的简单解释如下:
* select_type :表示 select 的类型,常用的取值有
simple (简单表,即不使用表连接或者子查询)、
primary (主查询,即外层的查询),
union (union 中的第二个或者后面的查询语句)、
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 (与 unque_subquery 类似,区别在于 in 的后面是查询非唯一索引字段的子查询)、
range (单表范围中的查询)、
index (对于前面的每一行,都通过查询索引来得到数据)、
all (对于前面的每一行,都通过全表扫描来得到数据)。
* possible_key :表示查询时,可能使用的索引。
* key :表示实际使用的索引。
* key_len : 索引字段的长度。
* rows :扫描行的数量。
* extra :执行情况的说明和描述
四、确定问题并采取相应的措施
经过以上步骤,基本就可以确认问题出现的原因。此时用户可以根据情况采取相应的措施,进行优化提高执行的效率。
在上面的例子中,已经可以确认是对 a 表的全表扫描导致效率的不理想,那么对 a 表的 age 字段创建索引,具体如下:
mysql> create index ind_sales_age on sales(age);
Query OK, 0 rows affected (0.11 sec)
Records: 0 Duplicates: 0 Warnings: 0
创建索引后,再看一下这条语句的执行计划,具体如下:
mysql> explain select sum(age) from sales a,company b where a.company_id = b.id and age = 20\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: a
type: ref
possible_keys: ind_sales_age
key: ind_sales_age
key_len: 2
ref: const
rows: 1
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: b
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: test1.a.company_id
rows: 1
Extra: Using index
2 rows in set (0.00 sec)
可以发现建立索引后a表需要扫描的行数明显减少(从65913行减少到1行),可见索引的使用可以大大提高数据库的访问速度,尤其在表很庞大的时候这种优势更为明显。