MySQL-优化-ICP(Index condition pushdown)详解

2024-02-23 01:32

本文主要是介绍MySQL-优化-ICP(Index condition pushdown)详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • MySQL-优化-ICP(Index condition pushdown)详解
    • 一、关于ICP
    • 二、什么情况下可以用到MySQL ICP特性
    • 三、疑问:如下这个sql为什么会使用到ICP呢?
    • 四、代码控制
      • server层处理
      • engine层处理

MySQL-优化-ICP(Index condition pushdown)详解

一、关于ICP

ICP是index condition pushdown的简称,目的是为了减少server层和Innodb层的交互次数,加快查询效率。
官方文档给了一个例子,如下
假设有一张表people的二级索引INDEX (zipcode, lastname, firstname),那么对于如下的查询

SELECT * FROM people
WHERE zipcode='95054'
AND lastname LIKE '%etrunia%'
AND address LIKE '%Main Street%';

MySQL可以利用索引来检索zipcode=‘95054’,但是没有办法通过索引查找来检索lastname LIKE ‘%etrunia%’,如果没有ICP的话,第一次交互过程中,查到所有的匹配zipcode='95054’的主键,第二次根据主键拿到整行数据信息,server层再根据整行数据来过滤lastname LIKE ‘%etrunia%’,address LIKE ‘%Main Street%’。如果有ICP的话,第一次交互的过程中,就可以根据lastname LIKE '%etrunia%'进行过滤了。

如上文档只是给了一个例子,而并不是说一定必须是组合索引,条件中包含了最左侧条件,然后是其他索引列的模糊匹配,我们知道非左前缀的模糊匹配,是无法通过B+树进行检索的,所以要通过ICP来进行判断。

二、什么情况下可以用到MySQL ICP特性

首先必要条件就是要打开ICP的开关,默认时开启的,如下

mysql> show global variables like '%optimizer_switch%'-> ;
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Variable_name    | Value                                                                                                                                                                                                                                                                                                                                                                                                              |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| optimizer_switch | index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=off,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=off,condition_fanout_filter=on,derived_merge=on |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)mysql>
mysql>
mysql> set global optimizer_switch='index_condition_pushdown=on'-> ;
Query OK, 0 rows affected (0.00 sec)mysql> show global variables like '%optimizer_switch%';
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Variable_name    | Value                                                                                                                                                                                                                                                                                                                                                                                                              |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| optimizer_switch | index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=off,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=off,condition_fanout_filter=on,derived_merge=on |
+------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

官方手册8.2.1.5 Index Condition Pushdown Optimization中有相关的介绍,关于什么时候可以利用到ICP特性的,如下

  • 只有在access type,也就是执行计划的type列为rang,ref,eq_ref,ref_or_null时,才有可能会用到ICP
  • ICP对于分区表同样生效,无论是Innodb,还是MyISAM。
  • 对于InnoDB类型的表来说,ICP只适用于二级索引,ICP的目的时为了读取整行数据的数量,减少IO,所以对于主键索引来讲,本身就是整行数据,所以不需要ICP
  • 不支持虚拟列上的索引
  • 不支持自查询
  • 引用存储函数的条件不能下推。存储引擎不能调用存储功能
  • 触发条件不能被下推,这点我也没有深入了解下,有时间看下。官方说明Triggered conditions cannot be pushed down. (For information about triggered conditions, see Section 8.2.2.4, “Optimizing Subqueries with the EXISTS Strategy”.)

下面对比下有无ICP时,数据检索过程的区别

关闭ICP时如下:

  • 读取二级索引元组,然后使用索引元组中的主键去聚集索引上定位整行数据
  • 拿其他的where条件来看此行数据是否满足,满足的话保留,不满足的话丢弃

打开ICP时如下:

  • 获取二级索引元组
  • 检测此二级索引元组是否满足下推的条件,如果不满足,读取下一条二级索引元组
  • 如果满足,通过此二级索引元组到主键索引中定位读取整行数据
  • 然后再检测此行数据是否满足其他未能下推的条件,如果满足保留,如果不满足,丢弃。

如果使用到了ICP的话,explain结果的extra列,会有Using index condition.

三、疑问:如下这个sql为什么会使用到ICP呢?

如下:

mysql> show create table test_in\G
*************************** 1. row ***************************Table: test_in
Create Table: CREATE TABLE `test_in` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(10) DEFAULT NULL,`age` int(11) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `age` (`age`),KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=61596517 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)mysql> explain select * from test_in where age in (1,2,3,4,5,6,7,8);
+----+-------------+---------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | test_in | NULL       | range | age           | age  | 5       | NULL |    8 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

可以看到并不是只有在多列索引中才会用到ICP特性,对于索引条件的in或者or的查询,都会用到。而下推的条件正是in或者or条件。

这种索引下推对性能有很大的提升吗?这部分正在测试研究中。

四、代码控制

关于ICP的流程控制大体上分为两部分,一是server层判断是否可以进行ICP,并且确定ICP的条件,传递给存储引擎;二是存储引擎去真正的执行ICP的过程。

server层处理

server层的处理在sql优化阶段,入口函数为QEP_TAB::push_index_cond(JOIN_TAB const*, unsigned int, Opt_trace_object*) sql_select.cc:1769,如下

 if (condition() &&tbl->file->index_flags(keyno, 0, 1) &HA_DO_INDEX_COND_PUSHDOWN &&hint_key_state(join_->thd, tbl, keyno, ICP_HINT_ENUM,OPTIMIZER_SWITCH_INDEX_CONDITION_PUSHDOWN) &&join_->thd->lex->sql_command != SQLCOM_UPDATE_MULTI &&join_->thd->lex->sql_command != SQLCOM_DELETE_MULTI &&!has_guarded_conds() &&type() != JT_CONST && type() != JT_SYSTEM &&!(keyno == tbl->s->primary_key &&tbl->file->primary_key_is_clustered())) //判断是否满足ICP的各种前置条件{DBUG_EXECUTE("where", print_where(condition(), "full cond",QT_ORDINARY););Item *idx_cond= make_cond_for_index(condition(), tbl,keyno, other_tbls_ok); //生成下推条件DBUG_EXECUTE("where", print_where(idx_cond, "idx cond", QT_ORDINARY););if (idx_cond){/*Check that the condition to push actually contains fields fromthe index. Without any fields from the index it is unlikelythat it will filter out any records since the conditions onfields from other tables in most cases have already beenevaluated.*/idx_cond->update_used_tables();if ((idx_cond->used_tables() & table_ref->map()) == 0){/*The following assert is to check that we only skip pushing theindex condition for the following situations:1. We actually are allowed to generate an index condition on anothertable.2. The index condition is a constant item.3. The index condition contains an updatable user variable(test this by checking that the RAND_TABLE_BIT is set).*/DBUG_ASSERT(other_tbls_ok ||                                  // 1idx_cond->const_item() ||                         // 2(idx_cond->used_tables() & RAND_TABLE_BIT) );     // 3DBUG_VOID_RETURN;}Item *idx_remainder_cond= 0;/*For BKA cache we store condition to special BKA cache fieldbecause evaluation of the condition requires additional operationsbefore the evaluation. This condition is used in JOIN_CACHE_BKA[_UNIQUE]::skip_index_tuple() functions.*/if (join_tab->use_join_cache() &&/*if cache is used then the value is TRUE only for BKA[_UNIQUE] cache (see setup_join_buffering() func).In this case other_tbls_ok is an equivalent ofcache->is_key_access().*/other_tbls_ok &&(idx_cond->used_tables() &~(table_ref->map() | join_->const_table_map))){cache_idx_cond= idx_cond;trace_obj->add("pushed_to_BKA", true);}else{idx_remainder_cond= tbl->file->idx_cond_push(keyno, idx_cond);DBUG_EXECUTE("where",print_where(tbl->file->pushed_idx_cond, "icp cond", QT_ORDINARY););}/*Disable eq_ref's "lookup cache" if we've pushed down an indexcondition. TODO: This check happens to work on current ICP implementations, butthere may exist a compliant implementation that will not work correctly with it. Sort this out when we stabilize the conditionpushdown APIs.*/if (idx_remainder_cond != idx_cond){ref().disable_cache= TRUE;trace_obj->add("pushed_index_condition", idx_cond);}Item *row_cond= make_cond_remainder(condition(), TRUE);DBUG_EXECUTE("where", print_where(row_cond, "remainder cond",QT_ORDINARY););if (row_cond){if (idx_remainder_cond)and_conditions(&row_cond, idx_remainder_cond);idx_remainder_cond= row_cond;}set_condition(idx_remainder_cond);trace_obj->add("table_condition_attached", idx_remainder_cond);}}

engine层处理

负责处理ICP逻辑在函数row_search_mvcc(unsigned char*, page_cur_mode_t, row_prebuilt_t*, unsigned long, unsigned long) row0sel.cc:5723,如下

	switch (row_search_idx_cond_check(buf, prebuilt, rec, offsets)) {case ICP_NO_MATCH:if (did_semi_consistent_read) {row_unlock_for_mysql(prebuilt, TRUE);}goto next_rec;case ICP_OUT_OF_RANGE:err = DB_RECORD_NOT_FOUND;goto idx_cond_failed;case ICP_MATCH:break;}

其中row_search_idx_cond_check来判断索引元组是否匹配下推的条件。

这篇关于MySQL-优化-ICP(Index condition pushdown)详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/737104

相关文章

Go语言使用select监听多个channel的示例详解

《Go语言使用select监听多个channel的示例详解》本文将聚焦Go并发中的一个强力工具,select,这篇文章将通过实际案例学习如何优雅地监听多个Channel,实现多任务处理、超时控制和非阻... 目录一、前言:为什么要使用select二、实战目标三、案例代码:监听两个任务结果和超时四、运行示例五

Linux线程同步/互斥过程详解

《Linux线程同步/互斥过程详解》文章讲解多线程并发访问导致竞态条件,需通过互斥锁、原子操作和条件变量实现线程安全与同步,分析死锁条件及避免方法,并介绍RAII封装技术提升资源管理效率... 目录01. 资源共享问题1.1 多线程并发访问1.2 临界区与临界资源1.3 锁的引入02. 多线程案例2.1 为

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件

python中列表应用和扩展性实用详解

《python中列表应用和扩展性实用详解》文章介绍了Python列表的核心特性:有序数据集合,用[]定义,元素类型可不同,支持迭代、循环、切片,可执行增删改查、排序、推导式及嵌套操作,是常用的数据处理... 目录1、列表定义2、格式3、列表是可迭代对象4、列表的常见操作总结1、列表定义是处理一组有序项目的