MySQL慢查询、索引优化

一、慢查询

1、什么是慢查询

- 如果查询一个语句超过设置的时间,日志就会存储到慢查询日志中.例如我系统设置的是10秒,那么我查询一条语句用了11秒,就属于慢查询的区域中了

2、如何查看慢查询时间

//显示慢查询的限时是10秒
mysql> show variables like "%long%";
+----------------------------------------------------------+-----------+
| Variable_name                                            | Value     |
+----------------------------------------------------------+-----------+
| long_query_time                                          | 10.000000 |
| performance_schema_events_stages_history_long_size       | 10000     |
| performance_schema_events_statements_history_long_size   | 10000     |
| performance_schema_events_transactions_history_long_size | 10000     |
| performance_schema_events_waits_history_long_size        | 10000     |
+----------------------------------------------------------+-----------+
5 rows in set (0.01 sec)

3、开启慢查询

// 查看是否开启了慢查询
mysql> show variables like "%slow%";
+---------------------------+----------------------------------------------------------+
| Variable_name             | Value                                                    |
+---------------------------+----------------------------------------------------------+
| log_slow_admin_statements | OFF                                                      |
| log_slow_slave_statements | OFF                                                      |
| slow_launch_time          | 2                                                        |
| slow_query_log            | OFF                                                      |
| slow_query_log_file       | /usr/local/mysql/data/Rainbow-ZhoudeMacBook-Pro-slow.log |
+---------------------------+----------------------------------------------------------+
5 rows in set (0.00 sec)

//启动慢查询

二、索引基本知识

1、索引的优点

  1. 大大减少了服务器需要扫描的数据量
  2. 帮助服务器避免排序和临时表
  3. 将随机io变成顺序io

2、索引的用处

  1. 快速查找匹配WHERE子句的行
  2. 从consideration中消除行,如果可以在多个索引之间进行选择,mysql通常会使用找到最少行的索引
  3. 如果表具有多列索引,则优化器可以使用索引的任何最左前缀来查找行
  4. 当有表连接的时候,从其他表检索行数据
  5. 查找特定索引列的min或max值
  6. 如果排序或分组时在可用索引的最左前缀上完成的,则对表进行排序和分组
  7. 在某些情况下,可以优化查询以检索值而无需查询数据行

3、索引的分类

  1. 主键索引
    • 主键的值做key的为主键索引.[不允许出现空值]
  2. 唯一索引
    • 唯一键做key.[允许出现空值]
  3. 普通索引
    • 不是主键也不是唯一的列做索引,就是普通索引.
  4. 全文索引
    • 很少用,例如like
  5. 组合索引
    • 又称’复合索引’,多个参数

4、面试技术名词

  1. 回表
    • [MySQL底层使用的是B+tree/hash indexes]如果索引是普通索引,where语句使用的时候在普通索引找到相对应的主键B+tree[key],然后去主键里面找数据进行返回.因为只有key对应的是整个数据,而其他索引的tree对应的都是key [如果有主键,就把主键当做key值;-> 如果没有主键,就把唯一键当做key值;->如果没有唯一键,则取一个6字节的row ID为key值]
    • 尽量不要使用回表,会慢.
  2. 覆盖索引
    • 以下两条MySQL语句,第一条是覆盖索引,第二条是回表.
    • 第一条因为ID是key值,select查的是id/name,根据where语句name可以直接返回想要查的id/name.
    • 第二条select的* 要返回全部数据,所以当where语句查到name的时候,里面只有对应的key值,所以需要回表进行根据key值再次查询,故底层查询了2次才返回的数据.
①:select id,name from table where name='Rainbow';
②:select * from table where name='Rainbow'
  1. 最左匹配
/**
 * 匹配最左,组合索引最左是name
 * ① 和 ②肯定能匹配
 * ③因为没有name而无法匹配
 * ④虽然顺序不同,但是优化器会调整顺序,所以匹配成功
 */
//KEY:代表此列是索引;  'name_index':组合索引的名字; ()内是索引的列
KEY 'name_index' ('name','age')
where name = ?              ✅
where name = ? and age = ?  ✅
where age = ?               ❌
where age = ? and name =?   ✅
  1. 索引下推
    • 原本在server层做筛选,现在到存储引擎中直接返回,速度更快.名词解释:索引下推
KEY 'name_index' ('name','age')
select * from table where name = ? and age = ?

5.6版本之前:
    - 先根据name去储存引擎中拿到所有的数据,然后再server层对age进行数据过滤
5.6版本之后:
    - 根据name,age两个列的值去获取数据,直接把数据返回
  1. 优化器
    • 1.CBO:基于成本的优化
    • 2.RBO:基于规则的优化

5、索引采用的数据结构

  1. 哈希表
  2. B+树

6、索引匹配方式

  1. 全值匹配
    • 全值匹配指的是和索引中的所有列进行匹配
/**
 * @key: 本次查询使用的索引
 */
KEY `idx_test` (`name`,`password`,`qq`)
mysql> explain select * from user where name = 'qqq' and password = 'qqq' and qq = 0;
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key      | key_len | ref               | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_test      | idx_test | 615     | const,const,const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.01 sec)
  1. 匹配最左前缀
    • 只匹配前面的几列[依旧可以使用该索引]
mysql> explain select * from user where name = 'qqq'  and qq = 0;
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key      | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_test      | idx_test | 403     | const |    1 |    10.00 | Using index condition |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
  1. 匹配列前缀
    • 可以匹配某一列的值的开头部分
mysql> explain select * from user where name like 'J%';
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | user  | NULL       | range | idx_test      | idx_test | 403     | NULL |    1 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
  1. 匹配范围值
    • 可以查找某一个范围的数据
mysql> explain select * from user where name > 'qqq';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ALL  | idx_test      | NULL | NULL    | NULL |   84 |    21.43 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

  1. 精确匹配某一列并范围匹配另外一列
    • 可以查询第一列的全部和第二列的部分
mysql> explain select * from user where name = 'qqq' and qq > 0;
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key      | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_test      | idx_test | 403     | const |    1 |    33.33 | Using index condition |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
  1. 只访问索引的查询
    • 查询的时候只需要访问索引,不需要访问数据行,本质上就是覆盖索引
mysql> explain select name,password,qq from user where name = 'qqq' and password = 'qqq' and qq = 0;
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key      | key_len | ref               | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------+
|  1 | SIMPLE      | user  | NULL       | ref  | idx_test      | idx_test | 615     | const,const,const |    1 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+----------+---------+-------------------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

三、哈希索引

1、概念

  1. 基于哈希表的实现,只有精确匹配索引所有列的查询才有效
  2. 在mysql中,只有memory的存储引擎显式支持哈希索引
  3. 哈希索引自身只需存储对应的hash值,所以索引的结构十分紧凑,这让哈希索引查找的速度非常快

2、哈希索引的限制

  1. 哈希索引只包含哈希值和行指针,而不存储字段值,索引不能使用索引中的值来避免读取行
  2. 哈希索引数据并不是按照索引值顺序存储的,所以无法进行排序
  3. 哈希索引不支持部分列匹配查找,哈希索引是使用索引列的全部内容来计算哈希值
  4. 哈希索引支持等值比较查询,也不支持任何范围查询
  5. 访问哈希索引的数据非常快,除非有很多哈希冲突,当出现哈希冲突的时候,存储引擎必须遍历链表中的所有行指针,逐行进行比较,直到找到所有符合条件的行
  6. 哈希冲突比较多的话,维护的代价也会很高

四、组合索引

  1. 当包含多个列作为索引,需要注意的是正确的顺序依赖于该索引的查询,同时需要考虑如何更好的满足排序和分组的需要

五、聚簇索引与非聚簇索引

1、聚簇索引

  1. 不是单独的索引类型,而是一种数据存储方式,指的是数据行跟相邻的键值紧凑的存储在一起

1、优点

  1. 可以把相关数据保存在一起
  2. 数据访问更快,因为索引和数据保存在同一个树中
  3. 使用覆盖索引扫描的查询可以直接使用页节点中的主键值

2、缺点

  1. 聚簇数据最大限度地提高了IO密集型应用的性能,如果数据全部在内存,那么聚簇索引就没有什么优势
  2. 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式
  3. 更新聚簇索引列的代价很高,因为会强制将每个被更新的行移动到新的位置
  4. 基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂的问题
  5. 聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候

2、非聚簇索引

  1. 数据文件跟索引文件分开存放

六、覆盖索引

1、基本介绍

  1. 如果一个索引包含所有需要查询的字段的值,我们称之为覆盖索引
  2. 不是所有类型的索引都可以称为覆盖索引,覆盖索引必须要存储索引列的值
  3. 不同的存储实现覆盖索引的方式不同,不是所有的引擎都支持覆盖索引,memory不支持覆盖索引

2、优势

  1. 索引条目通常远小于数据行大小,如果只需要读取索引,那么mysql就会极大的较少数据访问量
  2. 因为索引是按照列值顺序存储的,所以对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要少的多
  3. 一些存储引擎如MYISAM在内存中只缓存索引,数据则依赖于操作系统来缓存,因此要访问数据需要一次系统调用,这可能会导致严重的性能问题
  4. 由于INNODB的聚簇索引,覆盖索引对INNODB表特别有用

七、优化小细节

  1. 当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层
select actor_id from actor where actor_id=4;
//表达式↓  'actor_id + 1'
select actor_id from actor where actor_id+1=5;
  1. 尽量使用主键查询,而不是其他索引,因此主键查询不会触发回表查询
  2. union all,in,or都能够使用索引,但是推荐使用in
explain select * from actor where actor_id = 1 union all select * from actor where actor_id = 2;
explain select * from actor where actor_id in (1,2);
explain select * from actor where actor_id = 1 or actor_id =2;
  1. 范围列可以用到索引
    • 范围条件是:<、<=、>、>=、between
    • 范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
  2. 强制类型转换会全表扫描
//不会触发索引
explain select * from user where phone='13800001234';

//触发索引
explain select * from user where phone='13800001234';
  1. 更新十分频繁,数据区分度不高的字段上不宜建立索引
    • 更新会变更B+树,更新频繁的字段建议索引会大大降低数据库性能
    • 类似于性别这类区分不大的属性,建立索引是没有意义的,不能有效的过滤数据,
    • 一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算
  2. 创建索引的列,不允许为null,可能会得到不符合预期的结果
  3. 当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致
  4. 能使用limit的时候尽量使用limit
  5. 单表索引建议控制在5个以内
  6. 单索引字段数不允许超过5个(组合索引)
  7. 创建索引的时候应该避免以下错误概念
    • 索引越多越好
    • 过早优化,在不了解系统的情况下进行优化

八、索引监控

show status like 'Handler_read%';

1、参数解释

  1. Handler_read_first:读取索引第一个条目的次数
  2. Handler_read_key:通过index获取数据的次数
  3. Handler_read_last:读取索引最后一个条目的次数
  4. Handler_read_next:通过索引读取下一条数据的次数
  5. Handler_read_prev:通过索引读取上一条数据的次数
  6. Handler_read_rnd:从固定位置读取数据的次数
  7. Handler_read_rnd_next:从数据节点读取下一条数据的次数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值