MySql 优化——覆盖索引、索引条件下推、其他查询优化策略

1、覆盖索引

理解方式一:

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


理解方式二:

非聚族复合索引的一种形式,它包括在查询里的SELECT、JOIN和WHERE子句用到的所有列(即建索引的字段正好是覆盖查询条件中所涉及的字段)
简单说就是,索引列+主键、包含 SELECT 到 FROM之间查询的列。

看看下面这个例子:

例1:

CREATE INDEX idx_age_name ON student(age,NAME);

#测试1
EXPLAIN SELECT * FROM student WHERE age <> 20;

#测试2
EXPLAIN SELECT age FROM student WHERE age <> 20;

前面我们提到过,不等于<> 可能会导致索引失效,

测试一 妥妥的造成的索引失效

但是 测试2却使用到了索引

 因为第二种情况的查询字段是 age 他覆盖了索引 idx_age_name 的索引字段,

同理,如果我们查询的字段是 age,name, id(主键) 都会用到索引

我们验证一下:

EXPLAIN SELECT age,NAME,id FROM student WHERE age <> 20;

 ##在不等于的时候,可能会导致索引失效,但是到底用不用,都是由查询优化器决定的,

索引覆盖,会剪掉回表消耗的资源,所有,这种情况下,就算<>也会用上索引。

例2:

前面我们说过 like ‘%abc’ 百分号开头的模糊查询,会导致索引失效 如下:

CREATE INDEX idx_name ON student(NAME);
EXPLAIN SELECT * FROM student WHERE NAME LIKE '%abc';

此时,我们让被查询字段覆盖索引,如下: 

EXPLAIN SELECT id,age,NAME FROM student WHERE NAME LIKE '%abc';

覆盖索引的利弊
好处:
1避免Innodb表进行索引的二次查询 (回表)
Innodb是以聚集索引的顺序来存储的,对于lnnodb来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据,在查找到相应的键值后,还需通过主键进行二次查询才能获取我们真实所需要的数据。在覆盖索引中,二级索引的键值中可以获取所要的数据,避免了对主键的二次查询 ,减少了IO操作,提升了查询效率


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

由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。弊端:
索引字段的维护总是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。这是业务DBA,或者称为业务数据架构师的工作。

2、索引下推

1、如何使用

使用前后对比
Index Condition Pushdown(CP)是MySQL 5.6中新特性,是一种在存引层使用索引过滤数据的优化方式

  • 如果没有ICP,存储引擎会遍历索引以定位基表中的行,并将它们返回给 MySQL 服务器,由 MySQL 服务器评估WHERE 后面的条件是否保留行。
  • 启用ICP 后,如果部分 WHERE 条件可以仅使用索引中的列进行筛选,则 MVSOL 服务器会把这部分WHERE 条件放到存储引擎筛选。然后,存储引擎通过使用索引条目来筛选数据,并且只有在满足这一条件时才从表中读取行。

。好处: ICP可以减少存储引擎必须访问基表的次数和MySQL服务器必须访问存储引擎的次数。
。但是,ICP的 加速效果 取决于在存储引内通过 ICP筛选 的数据的比例.

例一:

参考这篇文章EXPLAIN语句分析 EXTRA ICP(using index condition)

例2:

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','000004',"六","赵","天津市");

 

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

我们看到,这个查询语句中用到了索引 zip_last_first,  key_len=63, 所以,有效的索引字段是zip,前面我们也说了 LIKE ‘%a’ 导致的索引失效。

但是此处用到了索引条件下推(ICP)  :
where 条件通过zipcode字段索引进行筛选,正常来说,要进一步回表(随机IO),来找到 lastname like %张%,和address like %北京市%,但是优化器判定进行回表的开销会大于直接利用索引条件比较lastname和address字段。

什么意思呢?

假设表中数据一共有一万条,通过where第一个条件(索引) 筛选之后,剩余还有十条记录,

如果这十条数据逐一进行回表,进一步筛选,就是要比较这一万条记录十次,而且这一万条记录很可能还剩分散存储的(聚簇索引,节点保存全部数据,导致单页存储能力有限)。(十次回表)

如果不回表,后面lastname 刚好又是索引字段的内容,仅仅在这十条记录里寻找符合条件的,肯定比回表效率要高的多,于是,优化器判断,让索引条件下推。(虽然索引字段用不上,但是可以用来进行筛选,过滤)。

通过上面两次筛选,还剩下2条数据,由于 address 不在联合索引的字段内,此时在进行回表查询,只需要进行2次回表即可。

#Extra中 using index condition  ,就是索引条件下推的体现,而后面那个using where ,其实就是address的回表操作的体现

##

ICP 的优点在于哪里?

: 一般在联合索引中,某些情况导致索引的部分字段失效,但是我们可以利用索引条件下推,使得在回表之前,对数据进一步的过滤。这样可以进一步减少回表的次数,减少随机IO的次数。

2、ICP开启与关闭


默认情况下启用索引条件下推。可以 通过设置系统变量 optimizer_switch 控制:index_condition_pushdown


#打开索引下推
SET optimizer_switch = 'index_condition_pushdown=on';
#关闭索引下推
SET optimizer_switch = 'index_condition_pushdown=off':


当使用索引条件下推时,EXPLAIN语句输出结果中 Extra 列内容显示为 Using index condition

 

3、 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优化方法,因为这种情况下使用ICP不会减少IO

7、相关子查询条件不能使用ICP

 

3、其他查询优化策略

1 EXISTS 和IN 的区分

MySql 优化——覆盖索引、索引条件下推、其他查询优化策略_第1张图片

 


问题:
不太理解哪种情况下应该使用 EXISTS,哪种情况应该用IN。选择的标准是看能否使用表的索引吗?回答:
索引是个前提,其实选择与否还是要看表的大小。你可以将选择的标准理解为 小表驱动大表。在这种方式下效率是最高的。

#前者 不相关子查询,后者相关子查询

此情况其实结构就算一种嵌套循环,驱动表取出数据,进入被驱动表内遍历搜索(前面说过,最好在被驱动表上建立索引),所以,驱动表越小,最终比较的次数就越少。

当A表小于B时,用EXISTS,因为EXISTS的实现,相当于外表循环,实现逻辑类似于:

for i in A

        for j  in B

                if j.cc==i.cc then ..

当B小于 A时 用 IN 因为实现逻辑类似

for i in B

        for j in A

                if( j.cc ==i.cc ) then 

简而言之,哪个表小,就用哪个表驱动, 外表小,就用Exists  ,内表小 就用 IN 

 

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


问:在MySQL 中统计数据表的行数,可以使用三种方式: SELECT COUNT(*)、SELECT COUNT(1)和 SELECTCOUNT(具体字段),使用这三者之间的查询效率是怎样的?
答:
前提: 如果你要统计的是某个字段的非空数据行数,则另当别论,毕竟比较执行效率的前提是结果一样才可以。
环节1: COUNT(*)和 COUNT(1) 都是对所有结果进行COUNT,COUNT(*)和COUNT(1)本质上并没有区别(二者执行时间可能略有差别,不过你还是可以把它俩的执行效率看成是相等的)。如果有 WHERE 子句,则是对所有符合筛选条件的数据行进行统计,如果没有 WHERE 子句,则是对数据表的数据行数进行统计。


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

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


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

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

3 关于SELECT(*)


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

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

2、无法使用覆盖索引


4 LIMIT 1 对优化的影响


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


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

5 多使用COMMIT


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

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

你可能感兴趣的:(数据库,mysql,sql,数据库)