MySQL查询优化

索引是什么

官方定义:索引(Index)是帮助MySQL高效获取数据的数据结构。

所以索引是一种数据结构,可以理解为“排好序的快速查找数据结构”。一般来说,索引本身也很大,不可能全部存储在内存,因此往往以索引文件的形式存储在磁盘上。

我们平常所说的索引,如果没有特别指明,都是B树(多路搜索树,不一定是二叉树)索引。其中聚集索引、次要索引、复合索引、前缀索引、唯一索引默认都是使用B+树

优势

类似书本的目录,提高查找效率,降低数据库IO成本。

通过索引列对数据排序,降低数据排序成本,降低CPU消耗。

劣势

  • 索引实际上也是一张表,保存了主键与索引字段,并指向实体表记录,所以也是要占用空间的。
  • 索引虽然大大提高了查询速度,同时会降低更新表的速度。因为更新表时,MySQL不仅要更新数据,还要更新索引。
  • 索引只是提高效率的一个因素,并不是建了索引或者用到了索引,查询就很快。

MySQL索引分类

  • 单值索引:即一个索引只包含单个列,一个表可以有多个单列索引。
  • 唯一索引:索引列的值必须唯一,但允许空值。
  • 复合索引:即一个索引包含多个列。

基本语法

创建
//如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定length
CREATE [UNIQUE] INDEX indexName ON tableName(columnName(length));
ALTER tableName ADD [UNIQUE] INDEX [indexName] ON (columnName(length));
删除
DROP INDEX [indexName] ON tableName;
查看
SHOW INDEX FROM tableName;

哪些情况需要/不需要建索引

  1. 主键自动建立唯一索引
  2. 频繁作为查询条件的字段应该创建索引
  3. 查询中与其它表关联的字段,外键关系建立索引
  4. 频繁更新的字段不适合创建索引
  5. Where条件后面用不到的字段不需要建立索引
  6. 需要排序的字段,索引是排好序的,所以通过索引排序将大大提高排序速度
  7. 统计或者分组的字段
  8. 表记录太少不要建立索引
  9. 经常增删的表不要建立索引
  10. 数据重复且分布均匀的字段不需要建立索引

性能分析

explain

explain是什么?

使用explain关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是怎么处理你的SQL语句的,使用explian+SQL可以看到以下信息

+------+-------------+---------+------+---------------+------+---------+------+------+-------+
| id   | select_type | table   | type | possible_keys | key  | key_len | ref  | rows | Extra |
+------+-------------+---------+------+---------------+------+---------+------+------+-------+

id:包含一组数字,表示查询或者操作表的顺序。id相同,执行顺序由上至下;id不同,如果是子查询,id的序号会递增,id越大,被执行优先级越高。

select_type:查询的类型,主要用于区别普通查询、联合查询、子查询等的复杂查询

  • SIMPLE:简单select查询,查询中不包含子查询或者UNION
  • PRIMARY:查询中若包含任何复杂的子部分,则最外层类型查询被标记为该类型
  • SUBQUERY:子查询,在select或者where列表中包含了子查询
  • DERIVED:在from列表中包含的子查询被标记为DERIVED(衍生),MySQL会递归执行这些子查询,把结果放在临时表里。
  • UNION:若第二个select出现在UNION之后,则被标记为UNION;若UNION包含在from子句的子查询中,外层select将被标记为DERIVED
  • UNION RESULT:从UNION表获取结果的select

table:显示是哪张表

type:显示查询使用了何种类型

+------+-------+-------+-----+--------+--------+---------+------+
| ALL  | index | range | ref | eq_ref | const  | system  | NULL |
+------+-------+-------+-----+--------+--------+---------+------+

从好到坏依次为:system>const>eq_ref>ref>range>index>ALL

  • system:表里只有一行记录(等于系统表),这是const类型的特例
  • const:const用于比较primary key或者union索引,因为只匹配一行数据,一次就能找到数据,所以很快
  • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或者唯一性索引扫描
  • ref:非唯一索引扫描,返回匹配某个单独值的所有行,可能返回多个,所以属于查找个扫描的混合体
  • range:只检索给定范围的行,使用一个索引来选择行,一般就是where条件后面出现了between、<、>、in等条件。这种不需要全索引扫描,所以比全索引扫描效率高
  • index:Full Index Scan,全索引扫描,因为索引文件通常比数据文件小,所以全索引扫描通常比全表扫描快
  • ALL:全表扫描

一般来说最少要达到range级别。

possible_keys:查询涉及到的字段若存在索引,则该索引将被列出,但不一定被实际使用

key:实际使用的索引,若为NULL则没又使用索引。查询中若使用了覆盖索引,则该索引和查询的select字段重叠

key_len:表示索引中使用的字节数,在不损失准确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际长度。即是根据表定义计算而得,不是通过表内检索得出。

ref:显示索引的哪一列被使用了

rows:根据表统计信息及索引选用情况,大致估算出找到所需要的记录需要读取的行数

Extra:包含不适合在其它列显示,但十分重要的信息

  • Using filesort:说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成排序的操作称为“文件排序”
  • Using temporary:使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表,常见于order by和group by
  • Using index:表示select操作中使用了覆盖索引,避免了访问表的数据行。如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表面索引用来读取数据而非查找。
  • Using wher:表明使用了where过滤
  • Using join buffer:使用了连接缓存
  • impossible where:表明where总是false,例如:where a=1 and a=2
  • select tables optimized away:在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化
  • distinct:优化distinct操作,在找到第一行匹配的元素后即停止找同样值的动作

避免索引失效

  • 如果索引使用了多列,最好按照索引字段的顺序从左至右使用,并且不跳过中间字段
  • 不在索引列上做任何操作(计算、函数、自动或者手动类型转换),否则会导致索引失效
  • 存储引擎不能使用索引中范围条件右边的列。(例如abc三个字段组成一个符合索引,条件为where a=1 and b>1 and c=1,则无法使用索引中c的列)
  • 尽量使用覆盖索引(只访问索引的查询),减少select *
  • MySQL在使用不等于(!=或者<>)、is null、is not null的时候无法使用索引
  • like以通配符开头的索引失效,则会变成全表扫描。(使用覆盖索引可以避免)
  • 少用or,用它连接时会使索引失效

order by 关键字

ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序,MySQL支持Index和FileSot两种方式排序,Index效率高,它指MySQL扫描索引本身完成排序。FileSort方式效率较低。

ORDER BY满足两种情况会使用Index方式排序:1.ORDER BY子句使用索引最左前列;2.使用WHERE子句与ORDER BY子句条件列组合满足索引最左前列。

如果不在索引列上,FileSort有两种算法:双路排序和单路排序

双路排序:MySQL4.1之前使用的是双路排序,就是两次扫描磁盘,最终得到数据。先从磁盘取排序字段,在buffer进行排序,再从磁盘中取出其它字段。取一批数据要进行扫描,IO是很耗时的,所以在MySQL4.1之后出现了第二种改进的算法,单路排序

单路排序:从磁盘中读取查询的所有列,在buffer对它们进行排序,然后扫描排序后的列表进行输出。避免两次IO,但是使用了更多空间。

单路排序需要更多的空间,所以可以通过增大sort_buffer_size和max_length_for_sort_data这两个参数来优化排序

group by 关键字

GROUP BY实质上是先排序后分组,所以策略基本跟ORDER BY一样。当无法使用索引列来分组,可以通过增大sort_buffer_size和max_length_for_sort_data这两个参数来优化分组。

慢查询日志

当通过explain不能解决SQL查询慢的问题,可以通过开启慢查询日志来继续排查

//查看慢日志是否开启ON为开启,OFF为关闭
show variables like '%slow_query_log%'
//开启慢查询日志
set global slow_query_log='ON';
set global slow_query_log=1;
//关闭慢查询日志
set global slow_query_log='OFF';
set global slow_query_log=0;
//查询当前的慢查询多少秒算慢
show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
//设置查询SQL超过5秒,记录慢查询日志,设置后要重新连接或者开个会话才生效
set global long_query_time=5;
//查询有多少条慢查询
show global status like '%slow_queries%';

show profile

用来分析SQL语句执行的资源消耗情况,默认关闭状态,保存最近15次的运行结果

show variables like 'profiling';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| profiling     | OFF   |
+---------------+-------+
//开启
set global profiling='ON';
set global profiling=1;
//关闭
set global profiling='OFF';
set global profiling=0;
show profiles;
+----------+------------+----------------------------------+
| Query_ID | Duration   | Query                            |
+----------+------------+----------------------------------+
|        1 | 0.00019383 | select @@version_comment limit 1 |
|        2 | 0.00052840 | show variables like 'profiling'  |
+----------+------------+----------------------------------+

show profile cpu,block io for query 1;
+----------------------+----------+----------+------------+--------------+---------------+
| Status               | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting             | 0.000065 | 0.000030 |   0.000029 |            0 |             0 |
| checking permissions | 0.000009 | 0.000005 |   0.000005 |            0 |             0 |
| Opening tables       | 0.000006 | 0.000003 |   0.000003 |            0 |             0 |
| After opening tables | 0.000013 | 0.000006 |   0.000006 |            0 |             0 |
| init                 | 0.000021 | 0.000011 |   0.000011 |            0 |             0 |
| optimizing           | 0.000015 | 0.000008 |   0.000007 |            0 |             0 |
| executing            | 0.000016 | 0.000008 |   0.000008 |            0 |             0 |
| end                  | 0.000009 | 0.000004 |   0.000004 |            0 |             0 |
| query end            | 0.000006 | 0.000004 |   0.000003 |            0 |             0 |
| closing tables       | 0.000005 | 0.000002 |   0.000003 |            0 |             0 |
| freeing items        | 0.000008 | 0.000004 |   0.000004 |            0 |             0 |
| updating status      | 0.000012 | 0.000007 |   0.000006 |            0 |             0 |
| cleaning up          | 0.000007 | 0.000003 |   0.000003 |            0 |             0 |
+----------------------+----------+----------+------------+--------------+---------------+

参数:

  • ALL 显示所有性能信息
  • BLOCK IO 显示块IO操作的次数
  • CONTEXT SWITCHES 显示上下文切换次数,不管是主动还是被动
  • CPU 显示用户CPU时间、系统CPU时间
  • IPC 显示发送和接收的消息数量
  • MEMORY [暂未实现]
  • PAGE FAULTS 显示页错误数量
  • SOURCE 显示源码中的函数名称与位置
  • SWAPS 显示SWAP的次数

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章