(十一)索引优化与查询优化(下)

索引优化与查询优化

  • 六、 GROUP BY优化
  • 七、优化分页查询
  • 八、优先考虑覆盖索引
    • 8.1 什么是覆盖索引?
    • 8.2 覆盖索引的利弊
  • 九、如何给字符串添加索引
    • 9.1 前缀索引
    • 9.2 前缀索引对覆盖索引的影响
  • 十、索引下推
    • 10.1 使用前后的扫描过程
    • 10.2 ICP的使用条件
    • 10.3 ICP使用案例
  • 十一、普通索引 vs 唯一索引
    • 11.1 查询过程
    • 11.2 更新过程
    • 11.3 change buffer的使用场景
  • 十二、其它查询优化策略
    • 12.1 EXISTS 和 IN 的区分
    • 12.2 COUNT(*)与COUNT(具体字段)效率
    • 12.3 关于SELECT(*)
    • 12.4 LIMIT 1 对优化的影响
    • 12.5 多使用COMMIT

六、 GROUP BY优化

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

七、优化分页查询

一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是limit 200o000,10,此时需要MySQL排序前2000010记录,仅仅返回2000000- 2000010的记录,其他记录丢弃,查询排序的代价非常大。

EXPLAIN SELECT * FROM student LIMIT 2000000,10;

在这里插入图片描述

优化思路一

在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容

EXPLAIN SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10) a 
WHERE t.id = a.id;

(十一)索引优化与查询优化(下)_第1张图片

优化思路二

该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询 。

EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;

在这里插入图片描述

八、优先考虑覆盖索引

8.1 什么是覆盖索引?

理解方式一:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了满足查询结果的数据就叫做覆盖索引,就是不用回表

理解方式二:非聚簇复合索引的一种形式,它包括在查询里的SELECT、JOIN和WHERE子句用到的所有列(即建索引的字段正好是覆盖查询条件中所涉及的字段)。

简单说就是, 索引列+主键 包含 SELECT 到 FROM之间查询的列

8.2 覆盖索引的利弊

好处:

  1. 避免Innodb表进行索引的二次查询(回表)

    Innodb是以聚集索引的顺序来存储的,对于Innodb来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据,在查找到相应的键值后,还需通过主键进行二次查询才能获取我们真实所需要的数据。

    在覆盖索引中,二级索引的键值中可以获取所要的数据,避免了对主键的二次查询,减少了I/O操作,提升了查询效率。

  2. 可以把随机IO变成顺序IO加快查询效率
    由于覆盖索引是按键值的顺序存储的,对于I0密集型的范围查找来说,对比随机从磁盘读取每一行的数据IO要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的IO转变成索引查找的顺序IO

    由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

弊端:

索引字段的维护总是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。这是业务DBA,或者称为业务数据架构师的工作

九、如何给字符串添加索引

有一张教师表,表定义如下:

create table teacher( 
	ID bigint unsigned primary key, 
	email varchar(64), 
	... 
)engine=innodb;

讲师要使用邮箱登录,所以业务代码中一定会出现类似于这样的语句:

mysql> select col1, col2 from teacher where email='xxx';

如果email这个字段上没有索引,那么这个语句就只能做 全表扫描

9.1 前缀索引

MySQL是支持前缀索引的。默认地,如果你创建索引的语句不指定前缀长度,那么索引就会包含整个字符串。

mysql> alter table teacher add index index1(email); 
#或
mysql> alter table teacher add index index2(email(6));

这两种不同的定义在数据结构和存储上有什么区别呢?下图就是这两个索引的示意图。
(十一)索引优化与查询优化(下)_第2张图片

以及
(十一)索引优化与查询优化(下)_第3张图片
如果使用的是index1(即email整个字符串的索引结构),执行顺序是这样的:

  1. 从index1索引树找到满足索引值是’ [email protected] ’的这条记录,取得ID2的值;
  2. 到主键上查到主键值是ID2的行,判断email的值是正确的,将这行记录加入结果集;
  3. 取index1索引树上刚刚查到的位置的下一条记录,发现已经不满足email=’ [email protected] ’的
    条件了,循环结束。

这个过程中,只需要回主键索引取一次数据,所以系统认为只扫描了一行。

如果使用的是index2(即email(6)索引结构),执行顺序是这样的:

  1. 从index2索引树找到满足索引值是’zhangs’的记录,找到的第一个是ID1; 2. 到主键上查到主键值是ID1的行,判断出email的值不是’ [email protected] ’,这行记录丢弃;
  2. 取index2上刚刚查到的位置的下一条记录,发现仍然是’zhangs’,取出ID2,再到ID索引上取整行然
    后判断,这次值对了,将这行记录加入结果集;
  3. 重复上一步,直到在idxe2上取到的值不是’zhangs’时,循环结束。

也就是说使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。前面已经讲过区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少。

9.2 前缀索引对覆盖索引的影响

结论:
使用前缀索引就用不上覆盖索引对查询性能的优化了,这也是你在选择是否使用前缀索引时需要考虑的一个因素。

十、索引下推

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

  • 如果没有ICP,存储引擎会遍历索引以定位基表中的行,并将它们返回给MySQL服务器,由MySQL服务器评估WHERE后面的条件是否保留行。
  • 启用ICP后,如果部分WHERE条件可以仅使用索引中的列进行筛选,则MySQL服务器会把这部分WHERE条件放到存储引擎筛选。然后,存储引擎通过使用索引条目来筛选数据,并且只有在满足这一条件时才从表中读取行。
  • 好处:ICP可以减少存储引擎必须访问基表的次数和MySQL服务器必须访问存储引擎的次数。
  • 但是,ICP的加速效果取决于在存储引擎内通过ICP筛选掉的数据的比例。

10.1 使用前后的扫描过程

在不使用ICP索引扫描的过程:

storage层:只将满足index key条件的索引记录对应的整行记录取出,返回给server层

server 层:对返回的数据,使用后面的where条件过滤,直至返回最后一行。

(十一)索引优化与查询优化(下)_第4张图片
(十一)索引优化与查询优化(下)_第5张图片

使用ICP扫描的过程:

  • storage层:

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

  • server 层:

    对返回的数据,使用table filter条件做最后的过滤。

(十一)索引优化与查询优化(下)_第6张图片
(十一)索引优化与查询优化(下)_第7张图片

使用前后的成本差别

使用前,存储层多返回了需要被index filter过滤掉的整行记录

使用ICP后,直接就去掉了不满足index filter条件的记录,省去了他们回表和传递到server层的成本。

ICP的 加速效果 取决于在存储引擎内通过 ICP筛选 掉的数据的比例。

作用:减少回表和随机IO的次数

10.2 ICP的使用条件

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优化方法。

10.3 ICP使用案例

  • 默认情况下启用索引条件下推。可以通过设置系统变量 optimizer_switch控制:index_condition_pushdown
#关闭索引下推
SET optimizer_switch = 'index_condition_pushdown=off';

#打开索引下推
SET optimizer_switch = 'index_condition_pushdown=on';
  • 当使用索引条件下推时,EXPLAIN语句输出结果中Extra列内容显示为Using index condition
CREATE TABLE `people` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `zipcode` VARCHAR(20) COLLATE utf8_bin DEFAULT NULL,
  `firstname` VARCHAR(20) COLLATE utf8_bin DEFAULT NULL,
  `lastname` VARCHAR(20) COLLATE utf8_bin DEFAULT NULL,
  `address` VARCHAR(50) COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `zip_last_first` (`zipcode`,`lastname`,`firstname`)
) ENGINE=INNODB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;

INSERT INTO `people` VALUES 
('1', '000001', '三', '张', '北京市'), 
('2', '000002', '四', '李', '南京市'), 
('3', '000003', '五', '王', '上海市'), 
('4', '000001', '六', '赵', '天津市');

**我们建立了一张表,再建立了一个索引如上,KEY zip_last_first(zipcode,lastname,firstname),这三个字段。

EXPLAIN SELECT * FROM people
WHERE zipcode='000001'
AND lastname LIKE '%张%'
AND address LIKE '%北京市%';

我们对这个查询语句进行分析,按照道理因为第二个条件是like %开头只会使用这个联合索引的第一个字段zipcode第二个字段的索引失效了,但是结果实际上用了两个字段,也就是lastname也用了,按照道理应该根据联合索引进行第一个字段查找,然后就进行回表和随机IO操作,但是因为索引下推,可以进行第二个字段的使用,也就是在索引目录页先进行第二个条件的匹配,然后再回表和随机IO,减少成本。

十一、普通索引 vs 唯一索引

从性能的角度考虑,你选择唯一索引还是普通索引呢?选择的依据是什么呢?

假设,我们有一个主键列为ID的表,表中有字段k,并且在k上有索引,假设字段 k 上的值都不重复。

这个表的建表语句是:

mysql> create table test( 
	id int primary key, 
	k int not null, 
	name varchar(16), 
	index (k) 
)engine=InnoDB;

表中R1~R5的(ID,k)值分别为(100,1)、(200,2)、(300,3)、(500,5)和(600,6)。

11.1 查询过程

假设,执行查询的语句是 select id from test where k=5。

  • 对于普通索引来说,查找到满足条件的第一个记录(5,500)后,需要查找下一个记录,直到碰到第一个不满足k=5条件的记录。
  • 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。

那么,这个不同带来的性能差距会有多少呢?答案是,微乎其微

11.2 更新过程

为了说明普通索引和唯一索引对更新语句性能的影响这个问题,介绍一下change buffer

当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下, InooDB会将这些更新操作缓存在change buffer中 ,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。

将change buffer中的操作应用到原数据页,得到最新结果的过程称为 merge。除了 访问这个数据页会触发merge外,系统有 后台线程会定期 merge。在 数据库正常关闭(shutdown) 的过程中,也会执行merge操作。

如果能够将更新操作先记录在change buffer, 减少读磁盘 ,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用 buffer pool 的,所以这种方式还能够 避免占用内存 ,提高内存利用率。

唯一索引的更新就不能使用change buffer ,实际上也只有普通索引可以使用。

如果要在这张表中插入一个新记录(4,400)的话,InnoDB的处理流程是怎样的?

11.3 change buffer的使用场景

  1. 普通索引和唯一索引应该怎么选择?其实,这两类索引在查询能力上是没差别的,主要考虑的是对 更新性能 的影响。所以,建议你 尽量选择普通索引

  2. 在实际使用中会发现, 普通索引change buffer 的配合使用,对于 数据量大 的表的更新优化还是很明显的。

  3. 如果所有的更新后面,都马上 伴随着对这个记录的查询 ,那么你应该 关闭change buffer 。而在其他情况下,change buffer都能提升更新性能。

  4. 由于唯一索引用不上change buffer的优化机制,因此如果 业务可以接受 ,从性能角度出发建议优先考虑非唯一索引。但是如果"业务可能无法确保"的情况下,怎么处理呢?

    • 首先, 业务正确性优先 。我们的前提是“业务代码已经保证不会写入重复数据”的情况下,讨论性能问题。如果业务不能保证,或者业务就是要求数据库来做约束,那么没得选,必须创建唯一索引。
      这种情况下,本节的意义在于,如果碰上了大量插入数据慢、内存命中率低的时候,给你多提供一个排查思路。
    • 然后,在一些“归档库 ”的场景,你是可以考虑使用唯一索引的。比如,线上数据只需要保留半年,然后历史数据保存在归档库。这时候,归档数据已经是确保没有唯一键冲突了。要提高归档效率,可以考虑把表里面的唯一索引改成普通索引。

十二、其它查询优化策略

12.1 EXISTS 和 IN 的区分

问题:

不太理解哪种情况下应该使用 EXISTS,哪种情况应该用 IN。选择的标准是看能否使用表的索引吗?

回答

索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为小表驱动大表。在这种方式下效率是最高的。
(十一)索引优化与查询优化(下)_第8张图片

12.2 COUNT(*)与COUNT(具体字段)效率

问:在 MySQL 中统计数据表的行数,可以使用三种方式: SELECT COUNT(*)SELECT COUNT(1) 和SELECT COUNT(具体字段),使用这三者之间的查询效率是怎样的?

前提:如果你要统计的是某个字段的非空数据行数,则另当别论,毕竟比较执行效率的前提是结果一样才可以。

环节1: COUNT(*)COUNT(1)都是对所有结果进行COUNTCOUNT( *)COUNT(1)本质上并没有区别(二者执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的)。如果有WHERE子句,则是对所有符合筛选条件的数据行进行统计;如果没有WHERE子句,则是对数据表的数据行数进行统计。

环节2:如果是MyISAM存储引擎,统计数据表的行数只需要O(1)的复杂度,这是因为每张MyISAM的数据表都有一个meta信息存储了row_count值,而一致性则由表级锁来保证。

如果是InnoDB存储引擎,因为InnoDB支持事务,采用行级锁和MCC机制,所以无法像MylISAM一样,维护一个row_count变量,因此需要采用扫描全表,是O(n)的复杂度,进行循环+计数的方式来完成统计。

环节3:在InnoDB引擎中,如果采用COUNT(具体字段)来统计数据行数,要尽量采用二级索引。因为主键采用的索引是聚簇索引,聚簇索引包含的信息多,明显会大于二级索引(非聚簇索引)。对于COUNT( *)和COUNT(1)来说,它们不需要查找具体的行,只是统计行数,系统会自动采用占用空间更小的二级索引来进行统计。

如果有多个二级索引,会使用key_len 小的二级索引进行扫描。当没有二级索引的时候,才会采用主键索引来进行统计。

12.3 关于SELECT(*)

在表查询中,建议明确字段,不要使用 *作为查询的字段列表,推荐使用SELECT <字段列表> 查询。原因:

① MySQL 在解析的过程中,会通过查询数据字典 将"*"按序转换成所有列名,这会大大的耗费资源和时间。

② 无法使用 覆盖索引

12.4 LIMIT 1 对优化的影响

针对的是会扫描全表的 SQL 语句,如果你可以确定结果集只有一条,那么加上LIMIT 1 的时候,当找到一条结果的时候就不会继续扫描了,这样会加快查询速度。

如果数据表已经对字段建立了唯一索引,那么可以通过索引进行查询,不会全表扫描的话,就不需要加上 LIMIT 1了。

12.5 多使用COMMIT

只要有可能,在程序中尽量多使用 COMMIT,这样程序的性能得到提高,需求也会因为COMMIT 所释放的资源而减少。

COMMIT 所释放的资源:

  • 回滚段上用于恢复数据的信息
  • 被程序语句获得的锁
  • redo / undo log buffer 中的空间
  • 管理上述 3 种资源中的内部花费

你可能感兴趣的:(MySQL高级,sql,数据库,database)