MySQL-索引优化与查询优化

SQL调优

1. 简单概括

  • 索引失效、没有充分利用到索引 ---索引建立
  • 关联查询太多JOIN(设计缺陷或不得已的需求) ---SQL优化
  • 服务器调优及各个参数设置(缓冲、线程数等) ---调整my.cnf
  • 数据过多 ---分库分表

2. 物理查询优化和逻辑查询优化

  • 物理查询优化是通过索引和表连接方式等技术来进行优化
  • 逻辑查询优化是通过SQL等价变换提升查询效率,就是换一种查询执行效率更高的写法

索引失效案例

1. 非索引字段全词匹配

2. 最佳左前缀法则

  • 一个索引可以包括16个字段
  • 对于多列索引,过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用
  • 如果查询条件中没有使用定义索引字段中的第1个字段时,多列(或联合)索引不会被使用。

3. 主键插入顺序

  • 当插入数据的主键忽大忽小时,会造成页面分裂和记录移位,产生性能损耗
  • 最好让插入记录的主键值依次递增,让主键具有AUTO_INCREMENT,存储引擎自己为表生成主键

4. 计算、函数、类型转换(自动或手动)导致索引失效

5. 类型转换导致索引失效

  • 假设字符串类型name字段上有索引
# 未使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name=123;
# 使用到索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name='123';

6. 范围条件右边的列索引失效

  • 最好将范围查询条件放置语句最后
# 只用到age,classId是索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;

7. 不等于(!= 或者<>)索引失效

8. is null可以使用索引,is not null无法使用索引

  • 最好在设计数据表的时候就将字段设置为NOT NULL约束
  • 比如将INT类型的字段,默认值设置为0。将字符类型的默认值设置为空字符串('')
  • 同理,在查询中使用not like也无法使用索引,导致全表扫描

9. like以通配符%开头索引失效

  • 页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决

10. OR 前后存在非索引的列,索引失效

11. 数据库和表的字符集统一使用utf8mb4

  • 统一字符集可以避免由于字符集转换产生的乱码
  • 不同的字符集进行比较前需要进行转换会造成索引失效

12. 查询建议

  • 对于单列索引,尽量选择针对当前query过滤性更好的索引
  • 在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好
  • 在选择组合索引的时候,尽量选择能够包含当前query中的where子句中更多字段的索引
  • 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面

关联查询优化

1. 对于内连接来讲

  • 如果表的连接条件中只能有一个字段有索引,则有索引的字段所在的表会被作为被驱动表出现
  • 在两个表的连接条件都存在索引的情况下,会选择小表作为驱动表

2. join语句原理

  • Index Nested-Loop Join:被驱动表t2的字段a上有索引,先遍历表t1,然后根据从表t1中取出的每行数据中的a值,去表t2中查找满足条件的记录
  • Simple Nested-Loop Join:当表t2的字段b上没有索引,因此执行流程时,每次到t2去匹配的时候,就要做一次全表扫描
  • Block Nested-Loop Join:被驱动表上没有可用的索引,把表t1的数据读入线程内存join_buffer中,把表t2中的每一行取出来,跟join_buffer中的数据做对比,满足join条件的,作为结果集的一部分返回。此种情况还是应该让小表当作驱动表
EXPLAIN SELECT * FROM t1 STRAIGHT_JOIN t2 ON (t1.a=t2.a);

3. 是否使用join语句

  • 如果可以使用Index Nested-Loop Join算法,也就是说可以用上被驱动表上的索引时可以使用
  • 如果使用Block Nested-Loop Join算法,扫描行数就会过多。尤其是在大表上的join操作,这样可能要扫描被驱动表很多次,会占用大量的系统资源。所以这种join尽量不要用
  • 判断要不要使用join语句时,就是看explain结果里面,Extra字段里面有没有出现“Block Nested Loop”

4. 驱动表的选择

  • 在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与join的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表

5. 几个结论

  • 保证被驱动表的JOIN字段已经创建了索引
  • 需要JOIN 的字段,数据类型保持绝对一致
  • LEFT JOIN 时,选择小表作为驱动表, 大表作为被驱动表 。减少外层循环的次数
  • INNER JOIN 时,MySQL会自动将 小结果集的表选为驱动表 。选择相信MySQL优化策略
  • 能够直接多表关联的尽量直接关联,不用子查询。(减少查询的趟数)
  • 不建议使用子查询,建议将子查询SQL拆开结合程序多次查询,或使用 JOIN 来代替子查询
  • 衍生表建不了索引

子查询优化

  • 执行子查询时,MySQL需要为内层查询语句的查询结果 建立一个临时表 ,然后外层查询语句从临时表中查询记录。查询完毕后,再 撤销这些临时表 。这样会消耗过多的CPU和IO资源,产生大量的慢查询
  • 子查询的结果集存储的临时表,不论是内存临时表还是磁盘临时表都 不会存在索引 ,所以查询性能会受到一定的影响
  • 对于返回结果集比较大的子查询,其对查询性能的影响也就越大
  • 在MySQL中,可以使用连接(JOIN)查询来替代子查询。连接查询 不需要建立临时表 ,其 速度比子查询要快 ,如果查询中使用索引的话,性能就会更好
  • 尽量不要使用NOT IN 或者 NOT EXISTS,用LEFT JOIN xxx ON xx WHERE xx IS NULL

排序优化

1. 在 WHERE 条件字段上加索引,但是为什么在 ORDER BY 字段上还要加索引呢?

  • SQL 中,可以在 WHERE 子句和 ORDER BY 子句中使用索引,目的是在 WHERE 子句中避免全表扫描 ,在 ORDER BY 子句避免使用 FileSort 排序
  • 尽量使用 Index 完成 ORDER BY 排序。如果 WHERE 和 ORDER BY 后面是相同的列就使用单索引列;如果不同就使用联合索引。
  • 无法使用 Index 时,需要对 FileSort 方式进行调优

2. order by不使用索引的情况

  • order by字段没有建立索引
  • order by时不limit,索引失效
  • order by时字段排列顺序错误,索引失效
  • order by时规则不一致, 索引失效 (顺序错,不索引;递增递减方向反,不索引)
  • 无过滤,不索引,order by字段没有进行条件过滤或者limit限制

3. filesort算法:双路排序和单路排序

  • 双路排序(慢):MySQL 4.1之前是使用双路排序 ,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和order by列 ,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
  • 单路排序(快):从磁盘读取查询需要的 所有列 ,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出

4. 优化策略

  • 提高sort_buffer_size
SHOW VARIABLES LIKE '%sort_buffer_size%';
  • 提高 max_length_for_sort_data
    • 但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘/O活动和低的处理器使用率
    • 如果需要返回的列的总长度大于max_length_for_sort_data,使用双路算法,否则使用单路算法
    • 可以在1024-8192字节之间调整
SHOW VARIABLES LIKE '%max_length_for_sort_data%';
  •  Order by 时select * 是一个大忌。最好只Query需要的字段

GROUP BY优化

  • group by 使用索引的原则几乎跟order by一致 ,group by 即使没有过滤条件用到索引,也可以直接使用索引。group by 先排序再分组,遵照索引建的最佳左前缀法则
  • 当无法使用索引列,增大 max_length_for_sort_data 和 sort_buffer_size 参数的设置
  • where效率高于having,能写在where限定的条件就不要写在having中了
  • 减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。Order by、group by、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。
  • 包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢

优化分页查询

EXPLAIN SELECT * FROM student LIMIT 2000000,10;
  • 方式一:在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容
SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10)
a WHERE t.id = a.id;
  • 方式二:适用于主键自增的表,可以把Limit 查询转换成某个位置的查询
SELECT * FROM student WHERE id > 2000000 LIMIT 10;

优先考虑覆盖索引

1. 覆盖索引概念

  • 当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了满足查询结果的数据就叫做覆盖索引
  • 非聚簇复合索引的一种形式,它包括在查询里的SELECT、JOIN和WHERE子句用到的所有列
    (即建索引的字段正好是覆盖查询条件中所涉及的字段)
  • 简单概括:索引列+主键 包含 SELECT 到 FROM之间查询的列

2. 覆盖索引的利弊

  • 好处:避免Innodb表进行索引的二次查询(回表);可以把随机IO变成顺序IO加快查询效率
  • 弊端:索引字段的维护总是有代价的

给字符串添加索引

1. 前缀索引

  • 默认情况,如果创建索引的语句不指定前缀长度,那么索引就会包含整个字符串
mysql> alter table teacher add index index1(email);
#或
mysql> alter table teacher add index index2(email(6));
  • 使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本
  • 区分度越高越好。因为区分度越高,意味着重复的键值越少

2. 前缀索引对覆盖索引的影响

  • 在使用前缀索引时,innoDB需要再回表一次确定查询结果是否正确
  • 因此,使用前缀索引就用不上覆盖索引对查询性能的优化

索引下推

1. 索引下推概念

  • Index Condition Pushdown(ICP)是MySQL 5.6中新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式
  • ICP可以减少存储引擎访问基表的次数以及MySQL服务器访问存储引擎的次数

2. 不使用ICP索引扫描的过程

  • storage层:只将满足index key条件的索引记录对应的整行记录取出,返回给server层
  • server 层:对返回的数据,使用后面的where条件过滤,直至返回最后一行

3.  使用ICP扫描的过程

  • storage层:首先将index key条件满足的索引记录区间确定,然后在索引上使用index filter进行过滤。将满足的index filter条件的索引记录才去回表取出整行记录返回server层。不满足index filter条件的索引记录丢弃,不回表、也不会返回server层
  • server 层:对返回的数据,使用table filter条件做最后的过滤

4. ICP的使用条件

  • 只能用于二级索引(secondary index)
  • explain显示的执行计划中type值(join 类型)为 range 、 ref 、 eq_ref 或者 ref_or_null 。
  • 并非全部where条件都可以用ICP筛选,如果where条件的字段不在索引列中,还是要读取整表的记录到server端做where过滤。
  • ICP可以用于MyISAM和InnnoDB存储引擎
  • MySQL 5.6版本的不支持分区表的ICP功能,5.7版本的开始支持。
  • 当SQL使用覆盖索引时,不支持ICP优化方法

5. ICP的开启

set optimizer_switch='index_condition_pushdown=on';

普通索引 vs 唯一索引

1. 查询过程

  • 对于普通索引来说,查找到满足条件的第一个记录后,需要查找下一个记录,直到碰到第一个不满足where条件的记录
  • 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索
  • 二者性能相差不大

2. change buffer介绍

  • 当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下, InooDB会将这些更新操作缓存在change buffer中 ,在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作
  • 将change buffer中的操作应用到原数据页,得到最新结果的过程称为 merge 。除了 访问这个数据页 会触发merge外,系统有 后台线程会定期 merge。在 数据库正常关闭(shutdown) 的过程中,也会执行merge操作
  • 将更新操作先记录在change buffer,减少读磁盘,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用buffer pool的,所以这种方式还能够避免占用内存,提高内存利用率
  • change buffer用的是buffer pool里的内存,因此不能无限增大。change buffer的大小,可以通过参数innodb_change_buffer_max-_size来动态设置
  • 这个参数设置为50的时候,表示change buffer的大小最多只能占用buffer pool的50%

3. 更新过程

  • 当更新记录的目标页在内存中时,唯一索引与普通索引性能相差不大
  • 当更新记录的目标页不在内存中时
    • 对于唯一索引来说,需要将数据页读入内存,判断有没有冲突,插入这个值,语句执行结束
    • 对于普通索引来说,则是将更新记录在change buffer,语句执行就结束了,效率更高

4. change buffer使用

  • change buffer只限于用在普通索引的场景下,而不适用于唯一索引
  • merge的时候才是真正进行数据更新的时刻,而change buffer的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做merge之前,change buffer记录的变更越多,收益就越大
  • 假设一个业务的更新模式是写入之后马上会做查询,那么即使满足了条件,将更新先记录在change buffer,但之后由于马上要访问这个数据页,会立即触发merge过程。这样随机访问IO的次数不会减少,反而增加了change buffer的维护代价,作用效果并不好

其它查询优化策略

1. EXISTS 与 IN 的选择

  • 选择标准为小表驱动大表
SELECT * FROM A WHERE cc IN(SELECT cc FROM B)
SELECT * FROM A WHERE EXISTS(SELECT cc FROM B WHERE B.cc=A.cc)
  • A表小就用EXISTS,exists实现逻辑 
for i in A 
    for j in B 
        if j.cc==i.cc then.…
  • B表小就用IN, in实现逻辑
for i in B 
    for j in A 
        if j.cc==i.cc then...

2. COUNT(*)与COUNT(具体字段)效率

  • 二者本质没有区别,执行效率几乎相等
  • 在InnoDB引擎中,如果采用COUNT(具体字段)来统计数据行数,要尽量采用二级索引。对于COUNT(*)和COUNT(1)来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计

3. 关于SELECT(*)

  • 在解析的过程中,会通过查询数据字典将"*"按序转换成所有列名,会大大的耗费资源和时间
  • 无法使用覆盖索引

4. LIMIT 1 对优化的影响

  • 针对的是会扫描全表的 SQL 语句,如果你可以确定结果集只有一条,那么加上 LIMIT 1 的时候,当找到一条结果的时候就不会继续扫描了,这样会加快查询速度。
  • 如果数据表已经对字段建立了唯一索引,那么可以通过索引进行查询,不会全表扫描的话,就不需要加上 LIMIT 1

5. 多使用COMMIT

  • 程序中尽量多使用 COMMIT,这样程序的性能得到提高,需求也会因为 COMMIT 所释放
    的资源而减少
  • COMMIT 所释放的资源:
    • 回滚段上用于恢复数据的信息
    • 被程序语句获得的锁
    • redo / undo log buffer 中的空间
    • 管理上述 3 种资源中的内部花费
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值