MySQL索引与Index Condition Pushdown

本文介绍MySQL/MariaDB中IndexConditionPushdown(ICP)查询优化技术,通过存储引擎侧的where条件筛选减少数据读取,显著提升查询性能。

大约在两年前,我写了一篇关于MySQL索引的文章。最近有同学在文章的评论中对文章的内容提出质疑,质疑主要集中在联合索引的使用方式上。在那篇文章中,我说明联合索引是将各个索引字段做字符串连接后作为key,使用时将整体做前缀匹配。

而这名同学在这个页面找到了如下一句话:index condition pushdown is usually useful with multi-column indexes: the first component(s) is what index access is done for, the subsequent have columns that we read and check conditions on。从而认为联合索引的使用方式与文中不符。

实际上,这个页面所讲述的是在MariaDB 5.3.3(MySQL是在5.6)开始引入的一种叫做Index Condition Pushdown(以下简称ICP)的查询优化方式。由于本身不是一个层面的东西,前文中说的是Index Access,而这里是Query Optimization,所以并不构成对前文正确性的影响。在写前文时,MySQL还没有ICP,所以文中没有涉及相关内容,但考虑到新版本的MariaDB或MySQL中ICP的启用确实影响了一些查询行为的外在表现。所以决定写这篇文章详细讲述一下ICP的原理以及对索引使用方式的优化。

实验

先从一个简单的实验开始直观认识ICP的作用。

安装数据库

首先需要安装一个支持ICP的MariaDB或MySQL数据库。我使用的是MariaDB 5.5.34,如果是使用MySQL则需要5.6版本以上。

Mac环境下可以通过brew安装:

 
  1. brew install mairadb

其它环境下的安装请参考MariaDB官网关于下载安装的文档

导入示例数据

与前文一样,我们使用Employees Sample Database,作为示例数据库。完整示例数据库的下载地址为:https://launchpad.net/test-db/employees-db-1/1.0.6/+download/employees_db-full-1.0.6.tar.bz2

将下载的压缩包解压后,会看到一系列的文件,其中employees.sql就是导入数据的命令文件。执行

 
  1. mysql -h[host] -u[user] -p < employees.sql

就可以完成建库、建表和load数据等一系列操作。此时数据库中会多一个叫做employees的数据库。库中的表如下:


 
  1. MariaDB [employees]> SHOW TABLES;
  2. +---------------------+
  3. | Tables_in_employees |
  4. +---------------------+
  5. | departments |
  6. | dept_emp |
  7. | dept_manager |
  8. | employees |
  9. | salaries |
  10. | titles |
  11. +---------------------+
  12. 6 rows in set (0.00 sec)

我们将使用employees表做实验。

建立联合索引

employees表包含雇员的基本信息,表结构如下:


 
  1. MariaDB [employees]> DESC employees.employees;
  2. +------------+---------------+------+-----+---------+-------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +------------+---------------+------+-----+---------+-------+
  5. | emp_no | int(11) | NO | PRI | NULL | |
  6. | birth_date | date | NO | | NULL | |
  7. | first_name | varchar(14) | NO | | NULL | |
  8. | last_name | varchar(16) | NO | | NULL | |
  9. | gender | enum('M','F') | NO | | NULL | |
  10. | hire_date | date | NO | | NULL | |
  11. +------------+---------------+------+-----+---------+-------+
  12. 6 rows in set (0.01 sec)

这个表默认只有一个主索引,因为ICP只能作用于二级索引,所以我们建立一个二级索引:


 
  1. ALTER TABLE employees.employees ADD INDEX first_name_last_name (first_name, last_name);

这样就建立了一个first_name和last_name的联合索引。

查询

为了明确看到查询性能,我们启用profiling并关闭query cache:


 
  1. SET profiling = 1;
  2. SET query_cache_type = 0;
  3. SET GLOBAL query_cache_size = 0;

然后我们看下面这个查询:


 
  1. MariaDB [employees]> SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man';
  2. +--------+------------+------------+-----------+--------+------------+
  3. | emp_no | birth_date | first_name | last_name | gender | hire_date |
  4. +--------+------------+------------+-----------+--------+------------+
  5. | 254642 | 1959-01-17 | Mary | Botman | M | 1989-11-24 |
  6. | 471495 | 1960-09-24 | Mary | Dymetman | M | 1988-06-09 |
  7. | 211941 | 1962-08-11 | Mary | Hofman | M | 1993-12-30 |
  8. | 217707 | 1962-09-05 | Mary | Lichtman | F | 1987-11-20 |
  9. | 486361 | 1957-10-15 | Mary | Oberman | M | 1988-09-06 |
  10. | 457469 | 1959-07-15 | Mary | Weedman | M | 1996-11-21 |
  11. +--------+------------+------------+-----------+--------+------------+

根据MySQL索引的前缀匹配原则,两者对索引的使用是一致的,即只有first_name采用索引,last_name由于使用了模糊前缀,没法使用索引进行匹配。我将查询联系执行三次,结果如下:


 
  1. +----------+------------+---------------------------------------------------------------------------+
  2. | Query_ID | Duration | Query |
  3. +----------+------------+---------------------------------------------------------------------------+
  4. | 38 | 0.00084400 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
  5. | 39 | 0.00071800 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
  6. | 40 | 0.00089600 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
  7. +----------+------------+---------------------------------------------------------------------------+

然后我们关闭ICP:


 
  1. SET optimizer_switch='index_condition_pushdown=off';

在运行三次相同的查询,结果如下:


 
  1. +----------+------------+---------------------------------------------------------------------------+
  2. | Query_ID | Duration | Query |
  3. +----------+------------+---------------------------------------------------------------------------+
  4. | 42 | 0.00264400 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
  5. | 43 | 0.01418900 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
  6. | 44 | 0.00234200 | SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man' |
  7. +----------+------------+---------------------------------------------------------------------------+

有意思的事情发生了,关闭ICP后,同样的查询,耗时是之前的三倍以上。下面我们用explain看看两者有什么区别:


 
  1. MariaDB [employees]> EXPLAIN SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man';
  2. +------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-----------------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-----------------------+
  5. | 1 | SIMPLE | employees | ref | first_name_last_name | first_name_last_name | 44 | const | 224 | Using index condition |
  6. +------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-----------------------+
  7. 1 row in set (0.00 sec)

 
  1. MariaDB [employees]> EXPLAIN SELECT * FROM employees WHERE first_name='Mary' AND last_name LIKE '%man';
  2. +------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-------------+
  5. | 1 | SIMPLE | employees | ref | first_name_last_name | first_name_last_name | 44 | const | 224 | Using where |
  6. +------+-------------+-----------+------+----------------------+----------------------+---------+-------+------+-------------+
  7. 1 row in set (0.00 sec)

前者是开启ICP,后者是关闭ICP。可以看到区别在于Extra,开启ICP时,用的是Using index condition;关闭ICP时,是Using where。

其中Using index condition就是ICP提高查询性能的关键。下一节说明ICP提高查询性能的原理。

原理

ICP的原理简单说来就是将可以利用索引筛选的where条件在存储引擎一侧进行筛选,而不是将所有index access的结果取出放在server端进行where筛选。

以上面的查询为例,在没有ICP时,首先通过索引前缀从存储引擎中读出224条first_name为Mary的记录,然后在server段用where筛选last_name的like条件;而启用ICP后,由于last_name的like筛选可以通过索引字段进行,那么存储引擎内部通过索引与where条件的对比来筛选掉不符合where条件的记录,这个过程不需要读出整条记录,同时只返回给server筛选后的6条记录,因此提高了查询性能。

下面通过图两种查询的原理详细解释。

关闭ICP

在不支持ICP的系统下,索引仅仅作为data access使用。

开启ICP

在ICP优化开启时,在存储引擎端首先用索引过滤可以过滤的where条件,然后再用索引做data access,被index condition过滤掉的数据不必读取,也不会返回server端。

注意事项

有几个关于ICP的事情要注意:

  • ICP只能用于二级索引,不能用于主索引。
  • 也不是全部where条件都可以用ICP筛选,如果某where条件的字段不在索引中,当然还是要读取整条记录做筛选,在这种情况下,仍然要到server端做where筛选。
  • ICP的加速效果取决于在存储引擎内通过ICP筛选掉的数据的比例。

参考

[1] https://mariadb.com/kb/en/index-condition-pushdown/

[2] http://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html


原文链接:http://blog.codinglabs.org/articles/index-condition-pushdown.html

<think>好的,我现在需要帮助用户理解索引下推(Index Condition Pushdown,ICP)的概念和工作原理,以及它在数据库优化中的作用。用户已经提供了三个相关的引用内容,我需要结合这些信息,同时参考之前的对话内容来构建回答。 首先,我应该明确索引下推的定义。根据引用[1]和[2],ICP是MySQL 5.6引入的优化技术,主要用于在查询过程中减少回表次数,尤其是在联合索引和范围查询的情况下。需要解释ICP在查询过程中的具体应用,比如当查询条件部分使用索引时,如何将过滤条件下推到存储引擎层处理,而不是在服务器层处理。 接下来,用户可能想知道ICP是如何工作的。根据引用[3]的例子,当使用联合索引时,存储引擎层可以处理更多的过滤条件,而无需将所有数据返回到服务器层。例如,联合索引(username, age),查询条件是username='bob' AND age=20。在没有ICP时,存储引擎会先找到所有username='bob'的记录,然后回表获取完整数据,再由服务器层过滤age=20。使用ICP后,存储引擎会在索引层直接应用age=20的条件,减少回表次数。 然后,我需要说明ICP如何优化查询。这包括减少回表次数和I/O操作,从而提升查询性能。特别是在范围查询或联合索引的情况下,ICP的优势更明显。例如,在范围查询后还有其他索引列的过滤条件时,ICP能有效减少需要回表的数据量。 还要提到ICP的适用场景和限制。根据引用[2],ICP适用于MyISAM和InnoDB引擎,且需要满足特定条件,比如查询涉及联合索引,并且部分条件可以被索引覆盖。同时,用户可能需要知道如何启用ICP(如MySQL中默认开启,或通过优化器开关控制)。 此外,用户之前的问题是关于索引失效的场景,所以在回答ICP时可以对比之前的失效情况,说明ICP如何帮助优化那些原本可能部分使用索引的情况。例如,当使用联合索引时,即使第二个条件无法有效使用索引,ICP仍能在引擎层过滤数据,减少回表次数,从而提升效率。 还需要考虑用户可能的疑问,比如ICP其他优化技术的区别,或者在实际应用中如何确认ICP是否生效。可以建议用户使用EXPLAIN语句查看执行计划,确认是否使用了ICP。 最后,总结ICP的作用和优势,确保回答结构清晰,分点说明,并引用提供的参考资料。同时,生成相关问题,引导用户进一步学习,比如如何验证ICP的效果,或者在不同数据库引擎中的应用差异。</think>### 索引下推(Index Condition Pushdown)详解 #### 一、基本概念 索引下推(Index Condition Pushdown, ICP)是数据库(如MySQL 5.6+)针对联合索引查询场景的优化技术,**核心目标是通过在存储引擎层提前过滤数据,减少回表次数和I/O开销**[^1]。 #### 二、工作原理(以联合索引$(a,b)$为例) 1. **传统查询流程** - 存储引擎通过索引找到满足$a$条件的所有记录的主键(如`a=1`) - 回表查询完整数据返回给Server层 - Server层再根据$b$的条件(如`b>5`)进行过滤 **问题**:即使$b$是索引列,仍需回表后过滤,浪费资源。 2. **启用ICP后的流程** - 存储引擎在索引层**同时应用$a$和$b$的条件**(如`a=1 AND b>5`) - 仅将同时满足两个条件的记录主键回表查询 - Server层直接使用最终结果 **优势**:减少回表数据量,降低磁盘I/O和网络传输开销[^2]。 #### 三、适用场景限制 | 场景类型 | 示例SQL | ICP作用效果 | |-----------------------|----------------------------------|--------------------------------| | 联合索引部分条件过滤 | `WHERE a=1 AND b>5` | 直接过滤$b$条件,减少回表次数 | | 范围查询后附加过滤 | `WHERE a>10 AND b=20` | 在索引层完成$b=20$的筛选 | | 覆盖索引无法完成查询 | `SELECT c FROM table WHERE a=1 AND b=5` | 需回表但减少回表次数 | **限制条件**: - 仅适用于二级索引(如MySQL的InnoDB) - 查询必须涉及索引列,且部分条件可下推 - 不适用于全文索引或空间索引[^3] #### 四、性能优化对比 通过具体示例说明优化效果: ```sql -- 表结构:`users`(id, username, age), 联合索引`idx_ua`(username, age) SELECT * FROM users WHERE username LIKE 'bob%' AND age = 25; ``` - **未启用ICP**: 存储引擎返回所有`username LIKE 'bob%'`的记录(1000条),回表后Server层过滤`age=25`(最终10条)。 **回表次数**:1000次 - **启用ICP**: 存储引擎在索引层直接筛选`username LIKE 'bob%' AND age=25`(10条),仅回表10次。 **I/O减少**:99%[^3] #### 五、配置验证 1. **启用/禁用ICP** MySQL默认启用ICP,可通过优化器开关控制: ```sql SET optimizer_switch = 'index_condition_pushdown=off'; -- 关闭 SET optimizer_switch = 'index_condition_pushdown=on'; -- 开启 ``` 2. **验证ICP生效** 使用`EXPLAIN`查看执行计划,若出现`Using index condition`即表示ICP生效: ```sql EXPLAIN SELECT * FROM users WHERE username LIKE 'bob%' AND age=25; ``` #### 六、实际应用建议 1. **优先使用覆盖索引**:若查询字段全在索引中,可避免回表(ICP效果更显著) 2. **合理设计联合索引顺序**:高频过滤条件放左侧,便于ICP发挥作用 3. **监控慢查询**:对未使用ICP的查询分析索引设计合理性
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值