• MySQL for OPS 03:索引和执行计划


    写在前面的话

    啥是索引?以一本书为例,如果想要找到某一指定章节的某一小节,书薄还好,如果书厚,可能就会找的头皮发麻。于是便出现了目录,让用户更容易查找到自己所需要的东西。索引就类似一张表的目录。其存在的意义就是优化查询速度,所以在学习的时候,只要一直记住这个类比,就相对更容易理解一些。

    关于索引

    在索引中,常见的算法有:B 树,Hash,R 树,Full text,GIS 等。只需要记两三个即可。

    其中最重要的就是 B 树索引,可再度分为:B- 树,B+ 树(在 > < like 等查找中性能更优),B* 树

    目前最多的就是 B+ 树,其结构可类比为树的:根(Root),枝(Internal),叶(Leaf)

    树状结构如图(图片来自互联网):

    在功能上面,索引可分为:辅助索引和聚集索引

    辅助索引创建 B 树过程:

    当用户执行创建索引操作:alter table t1 add index idx(id);

    1. 提取索引列(id 列)的值,进行排序。

    2. 向叶子节点申请数据页(16K),然后将排序后的值存储到叶子节点的数据页中,并和真实数据进行关联。

    3. 向枝节点和根节点申请数据页,将下层中单页最小值存储进去,如此类推。

    聚集索引创建 B 树过程:

    聚集索引创建的前提是列为主键或者唯一键,一般为主键,如 ID 字段。

    一旦表中有主键列,那么在数据写入的时候就会按照主键的顺序写到磁盘的数据页中。

    聚集索引叶节点直接就是真实数据页,减少了 B 树层级。

    两者的区别:

    聚集索引只能一个,要求非空唯一,一般是主键。辅助索引可以配置多个。

    聚集索引叶子节点直接就是真实数据页,辅助索引则是抽离出来排序后的数据。

    辅助索引分类:

    1. 普通的单列索引。

    2. 联合索引(多列索引),用多个列建立一个索引,如:select * from t1 where a=xx and b=xx and c=xx; 这种查询。

    3. 唯一索引(unique index):索引列的值都是唯一。

    索引树高度影响因素:

    1. 数据行数影响,解决办法:分库分表,分布式

    2. 索引列值过长,解决办法:前缀索引

    3. 数据类型:变长字符串使用了 char,解决办法:变长使用 varchar 

    管理索引

    查看表的索引情况:

    show index from city;
    -- 或者
    desc city;

    结果:

    其中:PRI 是主键索引(聚集索引),UNI 是唯一索引(辅助索引),MUL 是辅助索引。

    1. 创建普通索引:

    alter table city add index idx_name(name);

    此时查看创建情况:

    注意:同一列可以创建多个索引,但是没意义。同一个表中索引名字必须唯一。

    2. 删除索引:

    -- 创建一个测试索引
    alter table city add index idx_name1(name);
    
    -- 删除测试索引
    alter table city drop index idx_name1;

    3. 创建多列联合索引:

    alter table city add index idx_d_p(District,Population);

    此时查看:

    注意:索引列的顺序可能会影响 SQL 最终是否会走该索引。 

    4. 创建前缀索引:

    alter table city add index idx_di(District(5));

    注意:前缀索引必须是字符串,不能是数字:

     5. 创建唯一索引:

    alter table city add unique index idx_uni_id(id);

    查看:

    查看某列的重复情况以参考是否适合做索引列:

    select name,count(id) from city group by name having count(id)>1 order by count(id) desc;

    查看重复次数:

    压力测试:

    mysqlslap --defaults-file=/etc/my.cnf --concurrency=100 --iterations=1 --create-schema='库名' --query="select * from 库名.表名 where 字段名='值'" engine=innodb --number-of-queries=2000 -uroot -p -verbose

    100 个并发查询,一共查询 2000 次,没索引 100 万数据测试耗时 700s。加索引后为 0.1 秒,性能提升恐怖。

    关于执行计划

    执行计划的作用在于帮助分析 SQL 是否走索引以及是否走正确的的索引,进而优化业务逻辑。

    以 world 库为例,先删除之前 city 表创建的索引:

    查看表索引:

    看看数据库优化器最终选择的执行计划:

    desc select * from city where Name="Shanghai";

    只需在执行的 SQL 前加 desc:

    其中重要字段的含义:

    possible_keys:可能会用到的索引

    key:真正使用到的索引

    type:索引类型

    Extra:额外信息

    type 字段详解:(性能逐级提升)

    1. ALL:全表扫描,不走索引

    a. 查询没有索引的 name 字段:

    select * from city where Name="Shanghai";

    b. 查询语句中辅助索引使用到:<>not inlike '%xxx'

    desc select * from city where countrycode <> "CHN";
    desc select * from city where countrycode not in ("CHN","USA");
    desc select * from city where countrycode like "%CH%";

    如图:

    但在聚集索引字段使用则会走索引,如:

    desc select * from city where id <> 100;

    如图:

    2. INDEX:全索引扫描

    a. 查询需要获取整个索引树时:

    desc select countrycode from city;

    如图:

    b. 联合索引中,任何一个非最左列作为查询条件:

    假如索引 idx_a_b_c(a,b,c) 其实相当于建立了索引:a,ab,abc。此时 a 就是最左列,此时:

    select * from t1 where b=xxx;

    3. RANGE:索引范围扫描

    辅助索引:<,>,>=,<=,like,in,or

    主键:<>,not in

    注意:前两种能享受到 B+ 树的优势,第三种 in 方法不会,为了优化可以写成:

    desc select * from city where countrycode="CHN" union all select * from city where countrycode="USA";

    结果:

    4. ref:非唯一性索引,等值查询

    desc select * from city where countrycode="CHN";

    结果:

    5. eq_ref:多表连接时,连接条件使用唯一索引

    desc select a.name,b.name from city as a join country as b on a.countrycode=b.code where a.population<100;

    结果:

    6. const:唯一索引等值查询

    desc select * from city where id=10;

    结果:

    Extra 字段说明:

    desc select * from city where countrycode="CHN" order by population;

    在使用排序的时候会发现,在 extra 字段出现:filesort (文件排序)

    说明在使用 order by 的时是没有走索引的,之前说过,辅助索引会将索引列的值抽离出来进行排序。

    但 where 条件用了索引,由此得出:在多个条件的时候,优化器只会选择其中一个索引。

    解决办法:将多个条件中字段创建联合索引:

    alter table city add index idx_c_p(countrycode,population);
    desc select * from city where countrycode="CHN" order by population;

    再度查看:

    同理在 group by,distinct 中也一样。

    联合索引中的特殊情况:

    1. select * from t1 where a=xxx and b=xxx; 这种情况下,创建索引:

    alter table t1 add index idx_a_b(a,b); 和 alter table t1 add index idx_b_a(b,a); 效果一样。

    where 会自动调整顺序使其满足索引需求,但创建时应将唯一值更多放在前面。

    2. 如果 where 中出现不等值查询:select * from t1 where k1 > 100 and k2="aaa";

    创建索引应该:alter table t1 add index idx_2_1(k2,k1);

    并将语句 k2="aaa" 放前面,否则 > 在前会卡住索引,导致后面条件不走索引。

    3. 如果语句中存在多个子句,则需要按照执行顺序建立索引。

    线上数据库卡顿解决思路:

    1. show processlist; 或者 show full processlist; 查看问题 SQL,如果暂时无法解决,可先 kill 掉。

    2. 使用 desc 或者 explain 分析 SQL 走索引的情况,如果没走索引,通过分析后建立索引。

    特定的时间段慢的解决思路:

    可以通过后面的 slowlog 找到慢 SQL,分析语句走索引情况和耗时情况,进行修改。

    建立索引的原则

    1. 建表时一定要有主键,一般采用无关列。

    2. 尽量选择唯一性索引,如果非要使用重复值较多的,可将表逻辑拆分,也可将此列和其他列做联合索引。

    3. 为经常需要 where / order by / group by / join on 等操作的字段建立索引。

    4. 索引字段很长,尽量采用前缀索引。

    5. 索引不是越多越好,索引越多,越占空间,修改表越慢,可使用 percona-toolkit 的 pt-duplicate-key-checker 进行索引清理。

    6. 大表加索引需避开业务高峰期。

    7. 尽量避免在经常更新的列上面加索引。

    不走索引的情况

    1. 没有查询条件或者条件未建立索引。

    2. 查询结果集是原表中大部分数据,数据超过 25%,优化器会觉得没必要走索引。可使用 limit 或存 redis 解决。

    3. 索引失效,导致数据不真实。如果表经常修改,容易导致索引失效,需要删除重建解决。

    4. 查询条件使用函数在索引列上面,或则对索引列进行 + - * / 计算等,如:select * from t1 where id-1=9; 

    5. 隐式转换导致索引失效,如号码一般是 varchar 保存全数字,通过 select * from t1 where phone=120; 或 where phone='120'; 结果一致,但前者不走索引。

    6. <>,not in 是不走索引的,> < in 这些也有可能不走,后者尽量使用 union all 优化。

    7. like 中 % 在前面不走索引,如 like "%HN"。

    小结

    本章节索引和执行计划简直是 MySQL 优化中重中之重,当然在数据库小的时候还不明显,当数据量大起来以后,质的飞跃。 

  • 相关阅读:
    转来的:Diablo中的七大套装背景
    我的单元测试认识之路(下)
    《You can do it!》的chapter 2笔记
    一个枚举的简单应用
    仿百度的搜索下拉提示
    简单的 菜单当前选中 解决思路
    单元测试框架NUnit 之 Attributes特性(一)
    单元测试框架NUnit 之 constraints 约束
    分享个自己的ajax封装
    开源单元测试工具 Nunit
  • 原文地址:https://www.cnblogs.com/Dy1an/p/11381157.html
Copyright © 2020-2023  润新知