MySQL调优笔记 (一):索引优化,查询优化

/ 技术文章 / 0 条评论 / 1350浏览

MySQL调优笔记 (一):索引优化,查询优化

SQL语句性能下降原因

MySQL性能瓶颈

查询语句执行时间长,等待时间长可能有以下几种原因:

性能调优前提知识

索引

索引本质上是一种已排序的方便查找的数据结构

因此,索引具有以下优势:

但是,索引其实也是一张表,创建索引同样存在劣势:

一般除了主键索引,唯一键索引和外键索引,一般不建议使用单值索引,一般高并发下,推荐使用复合索引; 建议为其他表的关联字段、频繁作为查询条件的字段、排序、分组、统计字段创建索引。

不建议在以下情况创建索引:

工具:explain

开发过程中,遇到执行比较慢的SQL语句,可以使用explain这个命令来查看一个这些SQL语句的执行计划,查看该SQL语句有没有使用上了索引,有没有做全表扫描,这都可以通过explain命令来查看。

explain + SQL语句

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

其中各个字段的含义:

id

序列号,表示查询中执行select或操作的顺序

  1. id相同时,执行顺序由上至下
  2. 如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
  3. id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行

select_type

示查询中每个select子句的类型

  1. SIMPLE(简单SELECT,不使用UNION或子查询等)
  2. PRIMARY(查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
  3. UNION(UNION中的第二个或后面的SELECT语句)
  4. DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
  5. UNION RESULT(UNION的结果)
  6. SUBQUERY(子查询中的第一个SELECT)
  7. DEPENDENT SUBQUERY(子查询中的第一个SELECT,取决于外面的查询)
  8. DERIVED(派生表的SELECT, FROM子句的子查询)
  9. UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)

table

显示这一行的数据是关于哪张表的,有时不是真实的表名字,可能是别名或者derivedx

partitions

该列显示的为分区表命中的分区情况。非分区表该字段为空(null)。

type

表示MySQL在表中找到所需行的方式,又称“访问类型”。 常用的类型有: ALL < index < range < ref < eq_ref < const < system 以及 NULL( < 表示性能对比)

possible_keys

指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用

key

实际使用的索引,如果为NULL表示没有使用索引。如果查询中使用了覆盖索引(Extra标志位 Using index),则该索引和查询的select字段重叠。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用关键字FORCE INDEXUSE INDEX或者IGNORE INDEX

key_len

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

ref

表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值。

rows

表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数。

filtered

这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比。

Extra

这个列可以显示的信息非常多,有几十种,常用的有:

对于extra列,官网上有这样一段话:

If you want to make your queries as fast as possible, look out for Extra column values of Using filesort and Using temporary, or, in JSON-formatted EXPLAINoutput, for using_filesort and using_temporary_table properties equal to true.

大概的意思就是说,如果你想要优化你的查询,那就要注意extra辅助信息中的using filesort和using temporary,这两项非常消耗性能,需要注意。

实际生产中,group by操作特别容易产生using filesort和using temporary,分组时请务必按照索引的顺序和个数。

工具:Profile

mysql提供了可以用来分析当前会话中语句执行的资源消耗情况、生命周期等细节。默认情况下profile处于关闭状态,且保存最近15次运行结果。

例子:

mysql> select uid from u_user_master where `id_card` like '%233%';
+-----+
| uid |
+-----+
|  48 |
|  62 |
|  67 |
|  68 |
|  69 |
| 140 |
| 145 |
| 146 |
| 166 |
+-----+
9 rows in set (0.00 sec)

查看mysql性能

mysql> show profiles;
+----------+------------+------------------------------------------------------------+
| Query_ID | Duration   | Query                                    |
+----------+------------+------------------------------------------------------------+
|      1 | 0.00033300 | select uid from u_user_master where `id_card` like '%233%' |
|      2 | 0.00005900 | show version()                              |
|      3 | 0.00013300 | select version()                             |
|      4 | 0.00041800 | select uid from u_user_master where `id_card` like '%233%' |
+----------+------------+------------------------------------------------------------+
4 rows in set, 1 warning (0.00 sec)

第二列表示查询所用的时间。根据Query_ID 查看某个查询的详细时间耗费

mysql> show profile for query 4;
+----------------------+----------+
|  Status          | Duration |
+----------------------+----------+
| starting         | 0.000071 |
| checking permissions | 0.000007 |
| Opening tables     | 0.000016 |
| init            | 0.000023 |
| System lock       | 0.000020 |
| optimizing        | 0.000010 |
| statistics        | 0.000016 |
| preparing        | 0.000012 |
| executing        | 0.000003 |
| Sending data      | 0.000148 |
| end            | 0.000005 |
| query end        | 0.000006 |
| closing tables     | 0.000008 |
| freeing items      | 0.000020 |
| logging slow query   | 0.000041 |
| cleaning up       | 0.000012 |
+----------------------+----------+
16 rows in set, 1 warning (0.00 sec)

查看cpu、IO等信息

mysql> show profile block io, cpu for query 4;
+----------------------+----------+----------+------------+--------------+---------------+
|  Status          | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting         | 0.000071 | 0.000065 |   0.000005 |        0 |         0 |
| checking permissions | 0.000007 | 0.000005 |   0.000002 |        0 |         0 |
| Opening tables     | 0.000016 | 0.000016 |   0.000001 |        0 |         0 |
| init            | 0.000023 | 0.000021 |   0.000001 |        0 |         0 |
| System lock       | 0.000020 | 0.000009 |   0.000011 |        0 |         0 |
| optimizing        | 0.000010 | 0.000009 |   0.000002 |        0 |         0 |
| statistics        | 0.000016 | 0.000015 |   0.000001 |        0 |         0 |
|  preparing        | 0.000012 | 0.000011 |   0.000001 |        0 |         0 |
|  executing        | 0.000003 | 0.000001 |   0.000001 |        0 |         0 |
|  Sending data      | 0.000148 | 0.000148 |   0.000001 |        0 |         0 |
|  end            | 0.000005 | 0.000002 |   0.000002 |        0 |         0 |
|  query end        | 0.000006 | 0.000006 |   0.000001 |        0 |         0 |
| closing tables     | 0.000008 | 0.000007 |   0.000001 |        0 |         0 |
| freeing items      | 0.000020 | 0.000008 |   0.000012 |        0 |         0 |
|  logging slow query  | 0.000041 | 0.000022 |   0.000019 |        0 |         0 |
| cleaning up       | 0.000012 | 0.000011 |   0.000001 |        0 |         0 |
+----------------------+----------+----------+------------+--------------+---------------+
16 rows in set, 1 warning (0.00 sec)

如果详细信息中出现以下几种操作,说明该查询语句存在很大风险,非常耗资源

索引优化

单表优化

  1. 对性能出现问题的语句,使用explain命令行,分析问题原因
  2. 如果type出现All,表明该查询是全表扫描,需要优化,根据业务创建对应的复合索引,一般需要优化至ref或者range
  3. 修改索引后,查看possible_keys key,确认修改索引是否生效,该查询是否使用预期索引
  4. 如果extra出现using filesort,表明索引对于排序没有生效,需要优化索引
  5. 如果extra出现using temporary,表示使用了临时表存储中间结果,极度影响性能。

两表优化(两表join)

  1. left join,会返回所有左表结果,即使左表索引生效,仍旧会查询并返回所有左表记录,因此索引加在右表效果比较明显;
  2. right join,类似上面,right join索引应该加在左表
  3. 尽量减少join查询NestLoop循环总次数,即,永远使用小数据集驱动大数据集
  4. 优先优化NestLoop内层循环
  5. 当内存充足的情况下,可以适当调整JoinBuffer的设置

避免索引失效

  1. 查询语句需要符合最佳左前缀法则(查询从索引的最左列开始,并且不能跳过索引中的列)
    • 部分匹配可以触发索引,但必须严格按照索引顺序
    • 查询条件包含不存在字段不能触发索引
    • 查询条件顺序与索引顺序不同不能触发索引
  2. 不在索引列上做任何计算、函数、(自动或手动)类型装换,会导致索引失效而转向全表扫描
  3. 搜索引擎索引不能使用范围条件右边的列,范围条件可以放在索引和查询的最后
  4. 少用select *,尽量使用覆盖索引,即select 查询字段、顺序与索引保持一致或者符合最佳左前缀法则
  5. 尽量不适用 != 或 <> 会导致全表扫描
  6. is null 或者is not null不能使用索引
  7. like '%condition%' 不能使用索引,生产情况可以使用覆盖索引提高like性能
  8. 字符串不加单引号会导致索引失效 (发生自动类型转换)
  9. 少用or,使用or也会导致索引失效

查询优化

一般步骤:

  1. 运行程序1-2天,观察SQL运行情况
  2. 开启慢查询日志,设置阈值(>5s),抓取SQL语句
  3. explain 分析语句,调优索引或者语句
  4. 如果还是慢, show profile,查看SQL执行细节和生命周期
  5. 实在还是慢,调整mysql参数

如何打开慢查询日志:

优化原则:

Order by 例子:

假设表中有a, b, c,d四个字段,索引为 Key a_b_c(a,b,c)