5.MySQL查询语句优化

MySQL逻辑架构

  • MySQL逻辑架构总体分为三层,最上层为客户端层,并不是MySQL所独有,诸如:链接处理、受权认证、安全等功能均在这一层处理。html

  • MySQL大多数核心服务均在中间这一层,包括查询解析、分析、优化、缓存、内置函数(好比:时间、数学、加密等函数)。全部的跨存储引擎的功能也在这一层实现:存储过程、触发器、视图等。mysql

  • 最下层为存储引擎,其负责MySQL中的数据存储和提取。和Linux下的文件系统相似,每种存储引擎都有其优点和劣势。中间的服务层经过API与存储引擎通讯,这些API接口屏蔽了不一样存储引擎间的差别。sql

MySQL查询过程

语法解析和预处理

MySQL经过关键字将SQL语句进行解析,并生成一颗对应的解析树。这个过程解析器主要经过语法规则来验证和解析。好比SQL中是否使用了错误的关键字或者关键字的顺序是否正确等等。预处理则会根据MySQL规则进一步检查解析树是否合法。好比检查要查询的数据表和数据列是否存在等等。缓存

查询优化

通过前面的步骤生成的语法树被认为是合法的了,而且由优化器将其转化成查询计划。多数状况下,一条查询能够有不少种执行方式,最后都返回相应的结果。优化器的做用就是找到这其中最好的执行计划。安全

MySQL使用基于成本的优化器,它尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。在MySQL能够经过查询当前会话的last_query_cost的值来获得其计算当前查询的成本。服务器

mysql> select * from employees where first_name='Eric'  limit 1;
mysql> show status like 'last_query_cost';
+-----------------+--------------+
| Variable_name   | Value        |
+-----------------+--------------+
| Last_query_cost | 60795.999000 |
+-----------------+--------------+
1 row in set (0.00 sec)

mysql> select * from employees where emp_no=10226;
mysql> show status like 'last_query_cost';
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| Last_query_cost | 1.000000 |
+-----------------+----------+
1 row in set (0.00 sec)
 

示例中的第一个结果表示优化器认为大概须要作60795.999个数据页(?)的随机查找才能完成上面的查询。这个结果是根据一些列的统计信息计算得来的,这些统计信息包括:每张表或者索引的页面个数、索引的基数、索引和数据行的长度、索引的分布状况等等。session

The total cost of the last compiled query as computed by the query optimizer. This is useful for comparing the cost of different query plans for the same query. The default value of 0 means that no query has been compiled yet. The default value is 0. Last_query_cost has session scope.架构

有很是多的缘由会致使MySQL选择错误的执行计划,好比统计信息不许确、不会考虑不受其控制的操做成本(用户自定义函数、存储过程)、MySQL认为的最优跟咱们想的不同(咱们但愿执行时间尽量短,但MySQL值选择它认为成本小的,但成本小并不意味着执行时间短)等等。函数

查询执行引擎

在完成解析和优化阶段之后,MySQL会生成对应的执行计划,查询执行引擎根据执行计划给出的指令逐步执行得出结果。整个执行过程的大部分操做均是经过调用存储引擎实现的接口来完成,这些接口被称为handler API。查询过程当中的每一张表由一个handler实例表示。实际上,MySQL在查询优化阶段就为每一张表建立了一个handler实例,优化器能够根据这些实例的接口来获取表的相关信息,包括表的全部列名、索引统计信息等。存储引擎接口提供了很是丰富的功能,但其底层仅有几十个接口,这些接口像搭积木同样完成了一次查询的大部分操做。性能

返回结果给客户端

查询执行的最后一个阶段就是将结果返回给客户端。即便查询不到数据,MySQL仍然会返回这个查询的相关信息,好比改查询影响到的行数以及执行时间等等。

回头总结一下MySQL整个查询执行过程,总的来讲分为6个步骤:

  1. 客户端向MySQL服务器发送一条查询请求

  2. 服务器首先检查查询缓存,若是命中缓存,则马上返回存储在缓存中的结果。不然进入下一阶段

  3. 服务器进行SQL解析、预处理、再由优化器生成对应的执行计划

  4. MySQL根据执行计划,调用存储引擎的API来执行查询

  5. 将结果返回给客户端,同时缓存查询结果

查询语句优化

  1. MySQL不会使用索引的状况:非独立的列

mysql> explain SELECT * FROM employees.employees where emp_no=10226;
+----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table     | type  | possible_keys | key     | key_len | ref   | rows | Extra |
+----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+
|  1 | SIMPLE      | employees | const | PRIMARY       | PRIMARY | 4       | const |    1 | NULL  |
+----+-------------+-----------+-------+---------------+---------+---------+-------+------+-------+
1 row in set (0.00 sec)
mysql> explain SELECT * FROM employees.employees where emp_no+1=10227;
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 299335 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
1 row in set (0.00 sec)

  

2.前缀索引

若是列很长,一般能够索引开始的部分字符,这样能够有效节约索引空间,从而提升索引效率。

mysql> EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 299335 | Using where |
+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
1 row in set (0.00 sec)

  

根据选择性创建索引:

mysql> SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.0042 |
+-------------+
1 row in set (0.14 sec)
mysql> SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.9313 |
+-------------+
1 row in set (0.32 sec)
mysql> SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.7879 |
+-------------+
1 row in set (0.29 sec)
mysql> SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees;
+-------------+
| Selectivity |
+-------------+
|      0.9007 |
+-------------+
1 row in set (0.29 sec)
 

增长前缀索引:

mysql> ALTER TABLE employees.employees ADD INDEX `first_name_last_name4` (first_name, last_name(4));
Query OK, 0 rows affected (0.61 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';
+----+-------------+-----------+------+-----------------------+-----------------------+---------+-------------+------+------------------------------------+
| id | select_type | table     | type | possible_keys         | key                   | key_len | ref         | rows | Extra                              |
+----+-------------+-----------+------+-----------------------+-----------------------+---------+-------------+------+------------------------------------+
|  1 | SIMPLE      | employees | ref  | first_name_last_name4 | first_name_last_name4 | 22      | const,const |    1 | Using index condition; Using where |
+----+-------------+-----------+------+-----------------------+-----------------------+---------+-------------+------+------------------------------------+
1 row in set (0.00 sec)
 

3.多列索引

在多数状况下,在多个列上创建独立的索引并不能提升查询性能。理由很是简单,MySQL不知道选择哪一个索引的查询效率更好,因此在老版本,好比MySQL5.0以前就会随便选择一个列的索引,而新的版本会采用合并索引的策略。

mysql> explain SELECT * FROM employees.employees where emp_no < 10020 or first_name='Saniya';
+----+-------------+-----------+-------------+-------------------------------+-------------------------------+---------+------+------+--------------------------------------------------------------+
| id | select_type | table     | type        | possible_keys                 | key                           | key_len | ref  | rows | Extra                                                        |
+----+-------------+-----------+-------------+-------------------------------+-------------------------------+---------+------+------+--------------------------------------------------------------+
|  1 | SIMPLE      | employees | index_merge | PRIMARY,first_name_last_name4 | first_name_last_name4,PRIMARY | 16,4    | NULL |  275 | Using sort_union(first_name_last_name4,PRIMARY); Using where |
+----+-------------+-----------+-------------+-------------------------------+-------------------------------+---------+------+------+--------------------------------------------------------------+
1 row in set (0.00 sec)

  

4.覆盖索引

当发起一个索引覆盖的查询时,在EXPLAIN的Extra列能够看到“Using index”的信息。

mysql> explain select emp_no,first_name from employees where first_name = 'Saniya';
+----+-------------+-----------+------+-----------------------+-----------------------+---------+-------+------+--------------------------+
| id | select_type | table     | type | possible_keys         | key                   | key_len | ref   | rows | Extra                    |
+----+-------------+-----------+------+-----------------------+-----------------------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | employees | ref  | first_name_last_name4 | first_name_last_name4 | 16      | const |  256 | Using where; Using index |
+----+-------------+-----------+------+-----------------------+-----------------------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

  

5.使用索引扫描来排序

MySQL有两种方式能够生产有序的结果集,其一是对结果集进行排序的操做,其二是按照索引顺序扫描得出的结果天然是有序的。若是explain的结果中type列的值为index表示使用了索引扫描来作排序。

在设计索引时,若是一个索引既可以知足排序,又知足查询,是最好的。

只有当索引的列顺序和ORDER BY子句的顺序彻底一致,而且全部列的排序方向也同样时,才可以使用索引来对结果作排序。若是查询须要关联多张表,则只有ORDER BY子句引用的字段所有为第一张表时,才能使用索引作排序。ORDER BY子句和查询的限制是同样的,都要知足最左前缀的要求(有一种状况例外,就是最左的列被指定为常数),其余状况下都须要执行排序操做,而没法利用索引排序。

6.优化LIMIT分页

当须要分页操做时,一般会使用LIMIT加上偏移量的办法实现,同时加上合适的ORDER BY字句。若是有对应的索引,一般效率会不错,不然,MySQL须要作大量的文件排序操做。

一个常见的问题是当偏移量很是大的时候,好比:LIMIT 10000 20这样的查询,MySQL须要查询10020条记录而后只返回20条记录,前面的10000条都将被抛弃,这样的代价很是高。

优化这种查询一个最简单的办法就是尽量的使用覆盖索引扫描,而不是查询全部的列。而后根据须要作一次关联查询再返回全部的列。对于偏移量很大时,这样作的效率会提高很是大。


有时候若是可使用书签记录上次取数据的位置,那么下次就能够直接从该书签记录的位置开始扫描,这样就能够避免使用OFFSET,好比下面的查询:

代码块
SELECT id FROM t LIMIT 10000, 10;
改成:
SELECT id FROM t WHERE id > 10000 LIMIT 10;

 

参考资料:

1.我必须得告诉你们的MySQL优化原理

https://www.jianshu.com/p/d7665192aaaf

2.MySQL InnoDB索引选择性与统计信息

http://www.ywnds.com/?p=8378

3.MySQL服务与存储引擎间的接口

https://blog.csdn.net/itopcat/article/details/73614101

4.Writing a Custom Storage Engine

https://dev.mysql.com/doc/internals/en/custom-engine.html

相关文章
相关标签/搜索