• (七)MySQL索引优化


    建表语句

    CREATE TABLE staffs(
    id INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR (24) NOT NULL DEFAULT '' COMMENT '姓名',
    age INT NOT NULL DEFAULT 0 COMMENT '年龄',
    pos VARCHAR (20) NOT NULL DEFAULT 0 COMMENT '职位',
    add_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间'
    ) CHARSET utf8 COMMENT '员工记录表';
    
    
    INSERT INTO staffs(NAME, age, pos, add_time) VALUES('ronnie', 22, 'manager', NOW());
    INSERT INTO staffs(NAME, age, pos, add_time) VALUES('John', 23, 'devloper', NOW());
    INSERT INTO staffs(NAME, age, pos, add_time) VALUES('2181', 27, 'devloper', NOW());
    
    -- 建复合索引
    alter table staffs add index idx_staffs_nameAgePos(name, age, pos);
    

    mysql 优化法则

    1. 全值匹配我最爱
    2. 遵循最佳左前缀法则
    • 如果索引了多列, 查询从索引的最左前列开始并且不跳过索引中的列。
    • 其实索引本质是个单向链表, 你要先得到头才能逐渐往后取后面的, 中间断了就走不了后面的索引了。
    • 正确使用 Demo:
    mysql> explain select * from staffs where name = 'ronnie';
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
    | id | select_type | table  | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | staffs | NULL       | ref  | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 74      | const |    1 |   100.00 | NULL  |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where name = 'ronnie' and age = 22;
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-------+
    | id | select_type | table  | partitions | type | possible_keys         | key                   | key_len | ref         | rows | filtered | Extra |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-------+
    |  1 | SIMPLE      | staffs | NULL       | ref  | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 78      | const,const |    1 |   100.00 | NULL  |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where name = 'ronnie' and age = 22 and pos = 'manager';
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------------------+------+----------+-------+
    | id | select_type | table  | partitions | type | possible_keys         | key                   | key_len | ref               | rows | filtered | Extra |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------------------+------+----------+-------+
    |  1 | SIMPLE      | staffs | NULL       | ref  | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 140     | const,const,const |    1 |   100.00 | NULL  |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------------------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
    
    
    • 违反使用Demo:(完全失效)[带头大哥死了]:
    mysql> explain select * from staffs where age = 22 and pos = 'manager';
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where age = 22;
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where pos = 'manager';
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
      * 完全失效, 执行了全表扫描。
    
    • 违反使用Demo(部分失效)[中间兄弟断了]:
    mysql> explain select * from staffs where name = 'ronnie' and pos = 'manager';
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
    | id | select_type | table  | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra                 |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
    |  1 | SIMPLE      | staffs | NULL       | ref  | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 74      | const |    1 |    33.33 | Using index condition |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
    1 row in set, 1 warning (0.00 sec)
    
      * 使用了复合索引 idx_staffs_nameAgePos, 但 reference 只有一个常量参数, 只用了最左侧的 name, 没用到 pos。
    
    3. 不在索引列上做任何操作(计算、函数、(自动 or 手动) 类型转换), 会导致索引失效而转向全表扫描
    • 索引列上执行函数Demo:
    mysql> explain select * from staffs where left(name,4) = 'John';
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | Using where |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    
      * 可以看到执行了全表扫描, 索引失效。
    
    • 索引列上执行计算Demo:
    mysql> explain select * from staffs where char_length('name') > 3;
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
    | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | NULL  |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
    
      * 可以看到执行了全表扫描, 索引失效。
    
    • 索引列上执行类型转换Demo:
    mysql> select * from staffs where name = 2181;
    +----+------+-----+----------+---------------------+
    | id | NAME | age | pos      | add_time            |
    +----+------+-----+----------+---------------------+
    |  3 | 2181 |  27 | devloper | 2020-01-10 11:38:08 |
    +----+------+-----+----------+---------------------+
    1 row in set, 2 warnings (0.00 sec)
    
    mysql> explain select * from staffs where name = 2181;
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | idx_staffs_nameAgePos | NULL | NULL    | NULL |    3 |    33.33 | Using where |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    1 row in set, 3 warnings (0.00 sec)
    
    mysql> explain select * from staffs where name = '2181';
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
    | id | select_type | table  | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | staffs | NULL       | ref  | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 74      | const |    1 |   100.00 | NULL  |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
    
      * 这种就是比较坑的, 平时写sql是不注意, varChar类型请务必加上' ', 如果不加, mysql底层会进行类型的隐式转换, 造成索引失效。
    
    4. 存储引擎不能使用索引中范围条件右边的列
    • 范围之后全失效, 所以建索引是一门学问, 需要对常用的业务sql进行综合考量。
    • Demo:
    mysql> explain select * from staffs where name = 'John' and age > 22 and pos = 'devloper';
    +----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
    | id | select_type | table  | partitions | type  | possible_keys         | key                   | key_len | ref  | rows | filtered | Extra                 |
    +----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
    |  1 | SIMPLE      | staffs | NULL       | range | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 78      | NULL |    1 |    33.33 | Using index condition |
    +----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
    1 row in set, 1 warning (0.00 sec)
    
      * 我们的索引是这样建的: name -> age -> pos
      * 由于中间的age进行了范围查询, 会导致后续的索引全部失效。
    
    5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)), 减少select *
    • 对比Demo:
    mysql> explain select * from staffs where name = 'John' and pos = 'devloper';
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
    | id | select_type | table  | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra                 |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
    |  1 | SIMPLE      | staffs | NULL       | ref  | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 74      | const |    1 |    33.33 | Using index condition |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select name, age, pos from staffs where name = 'John' and pos = 'devloper';
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+--------------------------+
    | id | select_type | table  | partitions | type | possible_keys         | key                   | key_len | ref   | rows | filtered | Extra                    |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+--------------------------+
    |  1 | SIMPLE      | staffs | NULL       | ref  | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 74      | const |    1 |    33.33 | Using where; Using index |
    +----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+--------------------------+
    1 row in set, 1 warning (0.00 sec)
    
    
      * 可以看到extra多了 Using index, 表面select操作中使用了覆盖索引(Covering Index), 避免了访问表的数据行。
    
    6. mysql 在使用不等于(!= 或者 <>) 的时候无法使用索引会导致全表扫描
    • Demo:
    mysql> explain select * from staffs where name != 'John';
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | idx_staffs_nameAgePos | NULL | NULL    | NULL |    3 |    66.67 | Using where |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where name <> 'John';
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | idx_staffs_nameAgePos | NULL | NULL    | NULL |    3 |    66.67 | Using where |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    7. is null, is not null 也无法使用索引
    • 底层原因是: null 不能转换为索引表示的数字来索引
    • Demo:
    mysql> explain select * from staffs where name is null;
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra            |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
    |  1 | SIMPLE      | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | Impossible WHERE |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+------------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where name is not null;
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | idx_staffs_nameAgePos | NULL | NULL    | NULL |    3 |    66.67 | Using where |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    8.like 以通配符开头('%abc...') mysql 索引失效会变成全表扫描的操作
    • Demo:
    mysql> explain select * from staffs where name like '%ronnie';
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where name like '%ronnie%';
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
    +----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where name like 'ronnie%';
    +----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
    | id | select_type | table  | partitions | type  | possible_keys         | key                   | key_len | ref  | rows | filtered | Extra                 |
    +----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
    |  1 | SIMPLE      | staffs | NULL       | range | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 74      | NULL |    1 |   100.00 | Using index condition |
    +----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
    1 row in set, 1 warning (0.00 sec)
    
      * 可以看到只有% 在右边的查询使用了索引
      * 这个情况就比较蛋疼, 如果你们业务上就需要百分号在左边, 需要使用覆盖索引来优化。
    
          mysql> explain select id, name from staffs where name like '%ronnie%';
          +----+-------------+--------+------------+-------+---------------+-----------------------+---------+------+------+----------+--------------------------+
          | id | select_type | table  | partitions | type  | possible_keys | key                   | key_len | ref  | rows | filtered | Extra                    |
          +----+-------------+--------+------------+-------+---------------+-----------------------+---------+------+------+----------+--------------------------+
          |  1 | SIMPLE      | staffs | NULL       | index | NULL          | idx_staffs_nameAgePos | 140     | NULL |    3 |    33.33 | Using where; Using index |
          +----+-------------+--------+------------+-------+---------------+-----------------------+---------+------+------+----------+--------------------------+
          1 row in set, 1 warning (0.00 sec)
    
      * 做查询如果数据量大的话 用 Lucene, Solr, ElasticSearch 不香吗?
    
    9. 字符串不加单引号索引失效(mysql会自己做隐式转换)
    • Demo 同3
    10. 少用or, 用它来连接时会索引失效
    • Demo:
    mysql> explain select * from staffs where name = 'John' or name = 'ronnie';
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | idx_staffs_nameAgePos | NULL | NULL    | NULL |    3 |    66.67 | Using where |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where name = 'John' or age = 23;
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | idx_staffs_nameAgePos | NULL | NULL    | NULL |    3 |    55.56 | Using where |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql> explain select * from staffs where name = 'John' or pos = 'manager';
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    | id | select_type | table  | partitions | type | possible_keys         | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | staffs | NULL       | ALL  | idx_staffs_nameAgePos | NULL | NULL    | NULL |    3 |    55.56 | Using where |
    +----+-------------+--------+------------+------+-----------------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.01 sec)
    
    小总结

    优化总结口诀

  • 相关阅读:
    刷题0705 双指针思想归类
    基于Svelte3.x桌面端UI组件库Svelte UI
    Android Calendar 系统日历提醒、日程同步系统
    apk安装到系统文件夹
    Redis docker 主从模式与哨兵sentinel
    Nexus创建maven仓库导入maven依赖
    我们的力量的来源
    iOS项目嵌入Unity的View(Swift )
    面试题
    Alist+Raidriver+网盘
  • 原文地址:https://www.cnblogs.com/everyingo/p/13902175.html
Copyright © 2020-2023  润新知