面试问题首页投稿(暂停使用,暂停投稿)程序员

详解MySQL之SQL优化(1)

2017-02-24  本文已影响363人  BigfaceMonster

MySQL学习笔记(6)

SQL优化(1)

优化SQL的一般步骤

本文所涉及案例表来自MySQL的案例库sakila(官方提供的模拟电影出租厅信息管理系统的数据库),点击下载,压缩包包含sakila-schema.sql、sakila-data.sql和sakila.mwb分别为表结构,数据和MySQL Workbench模型。

通过show status了解SQL执行频率

查看服务器状态信息:show [session|global] status

session 代表当前连接,global为自数据库上次启动至今统计结果。

显示当前session中所有统计参数的值:

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_instance          | 0     |
| Com_alter_procedure         | 0     |
| Com_alter_server            | 0     |
| Com_alter_table             | 2     |

Com_xxx表示每个xxx语句执行次数,常关心:Com_select/insert/update/delete,以上对所有存储引擎的表都会累计。针对Innodb引擎,累加算法略有不同,分别为:Innodb_rows_read/inserted/updated/deleted。通过这些参数可以了解当前数据库是以插入更新为主还是查询操作为主,以及各类SQL执行比例。对于事务型应用,可通过Com_commit和Com_rollback了解事务提交和回滚的情况。

以下参数便于用户了解数据库基本情况:

定位执行效率低的SQL语句

通过EXPLAIN分析SQL的执行计划

找到效率低的SQL后,可通过EXPLAIN或者DESC命令获取MySQL如何执行SELECT语句的信息,如执行查询过程中表如何连接和连接顺序等,如:

mysql> desc select sum(amount) from customer a,payment b where 1=1 and a.customer_id=b.customer_id and email='JANE.BENNETT@sakilacustomer.org'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: a
   partitions: NULL
         type: ALL
possible_keys: PRIMARY
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 599
     filtered: 10.00
        Extra: Using where
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: b
   partitions: NULL
         type: ref
possible_keys: idx_fk_customer_id
          key: idx_fk_customer_id
      key_len: 2
          ref: sakila.a.customer_id
         rows: 26
     filtered: 100.00
        Extra: NULL
2 rows in set, 1 warning (0.01 sec)
    mysql> explain select * from (select * from customer where email='AARON.SELBY@sakilacustomer.org')a\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: customer
   partitions: NULL
         type: const
possible_keys: uk_email
          key: uk_email
      key_len: 153
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.01 sec)

MySQL4.1引入了explain extended,配合show warnings可以看到SQL真正被执行之前优化器做了哪些SQL改写。

MySQL5.1支持分区后,explain也对分区增加了支持,通过explain partition可查看SQL所访问的分区。

通过explain还不能定位SQL问题,此时可以选择profile的联合分析

通过show profile分析SQL

MySQL从5.0.37增加对show profilesshow profile的支持,通过have_profiling参数可查看是否支持profile。默认profiling是关闭的,可通过set在Session级别开启:

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

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)

通过profile可以更清楚了解SQL执行过程。通过show profile for query id可查看执行过程中线程的每个状态和耗时:

mysql> show profiles;
+----------+------------+-------------------------------+
| Query_ID | Duration   | Query                         |
+----------+------------+-------------------------------+
|        1 | 0.00013100 | SELECT DATABASE()             |
|        2 | 0.00013600 | SELECT DATABASE()             |
|        3 | 0.00040900 | show databases                |
|        4 | 0.00043100 | show tables                   |
|        5 | 0.00039900 | select count(*) from customer |
+----------+------------+-------------------------------+
5 rows in set, 1 warning (0.00 sec)

mysql> show profile for query 5;
+----------------------+----------+
| Status               | Duration |
+----------------------+----------+
| starting             | 0.000073 |
| checking permissions | 0.000009 |
| Opening tables       | 0.000020 |
| init                 | 0.000017 |
| System lock          | 0.000009 |
| optimizing           | 0.000192 |
| executing            | 0.000011 |
| end                  | 0.000007 |
| query end            | 0.000012 |
| closing tables       | 0.000010 |
| freeing items        | 0.000020 |
| cleaning up          | 0.000019 |
+----------------------+----------+
12 rows in set, 1 warning (0.00 sec)

MySQL支持进一步选择all、cpu、block io、context、switch、page faults等明细类型查看MySQL在使用什么资源上耗费过高时间:

mysql> show profile cpu for query 5;
+----------------------+----------+----------+------------+
| Status               | Duration | CPU_user | CPU_system |
+----------------------+----------+----------+------------+
| starting             | 0.000073 | 0.000067 |   0.000006 |
| checking permissions | 0.000009 | 0.000006 |   0.000003 |
| Opening tables       | 0.000020 | 0.000018 |   0.000001 |
| init                 | 0.000017 | 0.000016 |   0.000002 |
| System lock          | 0.000009 | 0.000008 |   0.000001 |
| optimizing           | 0.000192 | 0.000191 |   0.000002 |
| executing            | 0.000011 | 0.000008 |   0.000002 |
| end                  | 0.000007 | 0.000005 |   0.000001 |
| query end            | 0.000012 | 0.000012 |   0.000002 |
| closing tables       | 0.000010 | 0.000008 |   0.000001 |
| freeing items        | 0.000020 | 0.000008 |   0.000011 |
| cleaning up          | 0.000019 | 0.000018 |   0.000002 |
+----------------------+----------+----------+------------+
12 rows in set, 1 warning (0.00 sec)

还可通过source查看SQL解析过程中每步源码:

mysql> show profile source for query 5;
+----------------------+----------+-----------------------+----------------------+-------------+
| Status               | Duration | Source_function       | Source_file          | Source_line |
+----------------------+----------+-----------------------+----------------------+-------------+
| starting             | 0.000073 | NULL                  | NULL                 |        NULL |
| checking permissions | 0.000009 | check_access          | sql_authorization.cc |         835 |
| Opening tables       | 0.000020 | open_tables           | sql_base.cc          |        5649 |
| init                 | 0.000017 | handle_query          | sql_select.cc        |         121 |
| System lock          | 0.000009 | mysql_lock_tables     | lock.cc              |         323 |
| optimizing           | 0.000192 | optimize              | sql_optimizer.cc     |         151 |
| executing            | 0.000011 | exec                  | sql_executor.cc      |         119 |
| end                  | 0.000007 | handle_query          | sql_select.cc        |         199 |
| query end            | 0.000012 | mysql_execute_command | sql_parse.cc         |        5004 |
| closing tables       | 0.000010 | mysql_execute_command | sql_parse.cc         |        5056 |
| freeing items        | 0.000020 | mysql_parse           | sql_parse.cc         |        5630 |
| cleaning up          | 0.000019 | dispatch_command      | sql_parse.cc         |        1901 |
+----------------------+----------+-----------------------+----------------------+-------------+

通过trace分析优化器如何选择执行计划

MySQL5.6提供了对SQL跟踪trace,通过trace可了解优化器为何选择优化器A而不是B。使用方式:首先打开trace,格式为JSON,设置最大使用内存,避免不够不能完整显示解析过程;然后执行想做trace的SQL,最后检查INFORMATION_SCHEMA.OPTIMIZER_TRACE即可。

mysql> select @@optimizer_trace;
+-------------------------+
| @@optimizer_trace       |
+-------------------------+
| enabled=on,one_line=off |
+-------------------------+
1 row in set (0.00 sec)

mysql> set optimizer_trace_max_mem_size=1000000;
Query OK, 0 rows affected (0.00 sec)

mysql> select rental_id from rental where 1=1 and rental_date>='2005-05-25 04:00:00' and rental_date<='2005-05-25 05:00:00' and inventory_id=4466;
+-----------+
| rental_id |
+-----------+
|        39 |
+-----------+
1 row in set (0.01 sec)

mysql> select * from information_schema.optimizer_trace\G
*************************** 1. row ***************************
                            QUERY: select rental_id from rental where 1=1 and rental_date>='2005-05-25 04:00:00' and rental_date<='2005-05-25 05:00:00' and inventory_id=4466
                            TRACE: {
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
        ................

索引问题

索引是数据库优化最常用是最要方法之一,本节讨论MySQL索引分类、存储、使用的方法。

索引存储分类

索引是MySQL存储引擎层中实现的,不是在服务层。故每种引擎所用不一定完全相同,MySQL目前提供4中索引:

MySQL不支持函数索引,但可对列前某一部分进行索引,如title的前10个字符,该特性大大缩小了索引文件的大小,但是在排序分组时无法使用。

常用的索引为B-Tree索引和HASH索引,HASH只有Memory/Heap引擎支持,且只适用于Key-Value查询,通过Hash索引更快,但其不适用于范围查找,只有在where条件中是用“=”查找才会使用索引。

MySQL如何使用索引

B-Tree代表平衡树

MySQL中能使用索引的典型场景

mysql> desc select * from rental where rental_date='2005-05-25 17:22:10' and inventory_id=373 and customer_id=343\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: rental
   partitions: NULL
         type: const
possible_keys: rental_date,idx_fk_inventory_id,idx_fk_customer_id
          key: rental_date
      key_len: 10
          ref: const,const,const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)
mysql> desc select * from rental where customer_id>=373 and customer_id<400 \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: rental
   partitions: NULL
         type: range
possible_keys: idx_fk_customer_id
          key: idx_fk_customer_id
      key_len: 2
          ref: NULL
         rows: 718
     filtered: 100.00
        Extra: Using index condition
1 row in set, 1 warning (0.00 sec)

type为range表示选择范围查询,Extra列为Using where,表示优化器除了利用索引加速访问,还需要根据索引回表查询数据。

最左匹配原则可以算是MySQL中B-Tree索引使用的首要原则

mysql> desc select title from film_text where title like 'AFRICAN%'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_text
   partitions: NULL
         type: range
possible_keys: idx_title_desc_part,idx_title_description
          key: idx_title_desc_part
      key_len: 32
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

存在索引但不能使用索引的典型场景

mysql> desc select * from actor where last_name like '%IN%'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: actor
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 200
     filtered: 11.11
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

由于B-Tree索引的结构,故以%开头的查询无法利用索引,一般推荐使用全文索引(Fulltext)解决。

查看索引使用情况

如果索引正在工作,Handler_read_key的值将很高,该值代表一个行被索引值读取的次数,很低说明增加索引得到的性能改善不高。Handler_read_rnd_next值高以为查询运行低效,应该建索引补救,该值含义为数据文件读下一行的请求数。

两个简单实用优化方法

定期分析表和检查表

分析/检查表语法:

分析:
ANALYZE [LOCAL|NO_WRITE_TO_BINLOG] TABLE tbl_name [,tbl_name]...

检查:
CHECK TABLE tbl_name [,tbl_name] ... [option]...option={QUICK|FAST|MEDIUM|EXTENDED|CHANGED}

定期优化表

优化语法:

OPTIMIZE [LOCAL|NO_WRITE_TO_BINLOG] TABLE tbl_name [,tbl_name]...

如果已经删除表一部分或者对包含可变长度行的表进行了更改,应该实用OPTIMIZE TABLE优化表。该命令可将表中空间碎片合并,并消除由于删除更新操作造成的空间浪费。

对InnoDB引擎表,可通过设置innodb_file_per_table参数,设置InnoDB为独立表空间模式,这样每个数据库的每个表都会生成一个独立ibd文件,可一定程度上减轻InnoDB表的空间回收问题。
ANALYZE、CHECK、OPTIMIZE、ALTER TABLE执行期间会对表进行锁定,故注意在不忙的时候使用。

预告。。。
下一篇介绍常用SQL的优化,干货满满。

[完]

Github传送门

上一篇 下一篇

猜你喜欢

热点阅读