SQL优化的一般步骤

在MySQL数据库中,当面对一个有sql性能问题的数据库时,我们可以使用show status、explain、show profile等命令进行系统分析,尽快定位问题SQL并解决问题。

一、通过show status命令了解各种SQL的执行情况

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_alter_tablespace      | 0     |
| Com_alter_user            | 0     |
| Com_analyze               | 0     |
...

Com_xxx表示xxx语句执行的次数,通过观察Com_insert、Com_select、Com_update、Com_delete等参数可以了解当前数据库是以插入更新为主还是以查询为主。以及各种类型的SQL大致的执行比例是多少。
对于事务性应用,通过Com_commit、Com_rollback可以了解事务提交和回滚的情况,对于事务回滚非常频繁的数据库,意味着应用编写可能存在问题。

二、通过explain分析低效SQL的执行计划

通过慢查询日志可以定位那些执行效率较低的SQL语句。之后可以通过explain或者desc命令获取MySQL执行select语句的信息。

mysql> explain select * from demo where id>5 and id<10 \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: demo
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 3
        Extra: Using where
1 row in set (0.00 sec)

explain命令显示select语句的执行信息,下面对每个字段进行说明

1、select_type

表示select的类型,常见取值有:

  • select_type=SIMPLE:简单表,即不使用表连接和子查询
  • select_type=PRIMARY:主查询,即外层查询
  • select_type=UNION:UNION中的第二个或者后面的查询
  • select_type=SUBQUERY:子查询中的第一个select
2、table

输出结果集的表

3、type

表示MySQL在表中找到所需行的方式,或者叫访问类型。常见取值有:ALL、index、range、ref、eq_ref、const/system、NULL。从左到右,性能由最差到最好。

  • type=ALL:全表扫描,MySQL遍历全表来找到匹配的行
  • type=index:索引全扫描。MySQL遍历整个索引来查询匹配的行
  • type=range:索引范围扫描。常见于<、<=、>、>=、between等操作符
  • type=ref:使用非唯一索引扫描或唯一索引前缀扫描,返回匹配某个单独值的记录行
  • type=eq_ref:类似ref,区别在于使用的索引是唯一索引,对于每个索引的键值,表中只有一条记录匹配。
  • type=const/system:但表中最多有一个匹配行,查询起来非常迅速。常见于根据主键primary key或唯一索引unique index进行的查询。
  • type=NULL:MySQL不用访问表或者索引,直接就能得到结果。比如 select 2 > 1;
  • 其他
4、possible_keys

表示查询时可能使用的索引

5、key

表示实际使用的索引

6、key_len

使用到索引字段的长度

7、rows

扫描行的数量

8、Extra

执行情况的说明和描述,包含不适合在其他列中显示,但对执行计划非常重要的额外信息

使用explain extended命令加上show warnings,可以看到SQL真正被执行前优化做了哪些改写:

mysql> explain extended select sum(salary) from demo a , role b where 1=1 and a.id=b.id and a.sex='男
' \G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: b
         type: index
possible_keys: PRIMARY
          key: roleName_id
      key_len: 63
          ref: NULL
         rows: 6
     filtered: 100.00
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: a
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: test.b.id
         rows: 1
     filtered: 100.00
        Extra: Using where
2 rows in set, 1 warning (0.00 sec)

ERROR:
No query specified

mysql> show warnings \G;
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select sum(`test`.`a`.`salary`) AS `sum(salary)` from `test`.`demo` `a` join
`test`.`role` `b` where ((`test`.`a`.`id` = `test`.`b`.`id`) and (`test`.`a`.`sex` = '男'))
1 row in set (0.00 sec)

通过show warning命令的message字段可以看到优化器去除了1=1的恒等条件,在遇到复杂SQL可以通过explain extended命令加show warning命令迅速获得一个清晰易读的语句

三、通过show profile分析SQL

通过profile,我们可以更清楚地了解SQL的执行过程,及各个过程所花时间。
查看当前MySQL是否支持profile:

mysql> select @@have_profiling;
+------------------+
| @@have_profiling |
+------------------+
| YES              |
+------------------+
1 row in set, 1 warning (0.00 sec)

默认profiling是关闭的,可以通过set语句在Session级别开启profiling:

mysql> select @@profiling;
+-------------+
| @@profiling |
+-------------+
|           0 |
+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> set profiling=1;
Query OK, 0 rows affected, 1 warning (0.00 sec)

在一个InnoDB表上查询总行数:

mysql> select count(*) from tb_item;
+----------+
| count(*) |
+----------+
|     3096 |
+----------+
1 row in set (0.01 sec)

通过show profiles语句查看当前查询语句的Query ID:

mysql> show profiles;
+----------+------------+------------------------------+
| Query_ID | Duration   | Query                        |
+----------+------------+------------------------------+
|        1 | 0.00025375 | SELECT DATABASE()            |
|        2 | 0.00505650 | select count(*) from tb_item |
+----------+------------+------------------------------+
2 rows in set, 1 warning (0.00 sec)

通过show profile for query 2 看到执行过程中线程的执行状态和消耗的时间:

mysql> show profile for query 2;
+----------------------+----------+
| Status               | Duration |
+----------------------+----------+
| starting             | 0.000117 |
| checking permissions | 0.000012 |
| Opening tables       | 0.000028 |
| init                 | 0.000021 |
| System lock          | 0.000015 |
| optimizing           | 0.000009 |
| statistics           | 0.000023 |
| preparing            | 0.000019 |
| executing            | 0.000005 |
| Sending data         | 0.004631 |
| end                  | 0.000017 |
| query end            | 0.000013 |
| closing tables       | 0.000022 |
| freeing items        | 0.000099 |
| cleaning up          | 0.000028 |
+----------------------+----------+
15 rows in set, 1 warning (0.00 sec)

为了更加直观看到排序结果,可以查询information_schema.profiling表并按消耗时间做个desc排序:

mysql> select state,sum(duration) as Total_R ,
    ->  round(
    ->          100*sum(duration)/(select sum(duration) from information_schema.profiling
    ->                  where query_id = 2),2
    ->          )as Pct_R,
    ->  count(*)
    ->  from information_schema.profiling
    ->  where query_id=2
    ->  group by state
    ->  order by Total_R desc;
+----------------------+----------+-------+----------+
| state                | Total_R  | Pct_R | count(*) |
+----------------------+----------+-------+----------+
| Sending data         | 0.004631 | 91.54 |        1 |
| starting             | 0.000117 |  2.31 |        1 |
| freeing items        | 0.000099 |  1.96 |        1 |
| cleaning up          | 0.000028 |  0.55 |        1 |
| Opening tables       | 0.000028 |  0.55 |        1 |
| statistics           | 0.000023 |  0.45 |        1 |
| closing tables       | 0.000022 |  0.43 |        1 |
| init                 | 0.000021 |  0.42 |        1 |
| preparing            | 0.000019 |  0.38 |        1 |
| end                  | 0.000017 |  0.34 |        1 |
| System lock          | 0.000015 |  0.30 |        1 |
| query end            | 0.000013 |  0.26 |        1 |
| checking permissions | 0.000012 |  0.24 |        1 |
| optimizing           | 0.000009 |  0.18 |        1 |
| executing            | 0.000005 |  0.10 |        1 |
+----------------------+----------+-------+----------+
15 rows in set (0.00 sec)

可以清晰地看到时间主要消耗在Sending data这个状态上。原因是MySQL线程要在该状态做大量的磁盘读取操作。

猜你喜欢

转载自blog.csdn.net/weixin_41172473/article/details/83307956