前言
计算下推是数据库优化器优化查询性能的一种常见手段,早期的数据库系统提及的计算下推一般是指谓词下推,其
理论源自关系代数理论。
2000
年以后,随着
Oracle RAC
的盛行以
及一众开源分布式数据库的崛起,存算分离的概
念逐步流行,计算下推的涵盖范围由此从基本的谓词
投影下推延伸到了数据库所支持的一切可能计算的下推
(
JOIN
、聚合、完整
query
、部分
query
等)。
对于单机数据库来说,尤其是
MySQL
这种采用经典火山模型的关系型数据库,最常见的就是谓词下推
、投影下
推,通常在查询优化的
RBO
阶段完成(有的下推在
CBO
阶段
),
通过将
Filter
和
Project
算子在抽象语法树
(
AST
)中向下移动,提前对行
/
列进行裁剪,减少后续计算的数据量。
当然,
MySQL
中不仅仅是谓词下推、投
影下推,还有条件下推、
ICP
等,本文就盘点一下
MySQL
生态中有哪些计
算下推。
MySQL
原生方案
本小节介绍
MySQL
社区版中的计算下推方案。
1.
索引条件下推
ICP
功能介绍
ICP
(
Index Condition Pushdown
,索引下推
),
是
MySQL 5.6**
版本
**
推出的功能,用于优化
MySQL
查询。
ICP
可以减少存储引擎查询回表的次数以及
MySQL server
层
访问存储引擎的次数。
ICP
的目标是
**
减少整行记录读取的次数,从而减少
****I/O
操作
**
。
在没有使用
ICP**
的情况下,索引(二级索引)扫描的过
程如下:
**
存储引擎读取二级索引记录
(不是完整行
)
;
根据二级索引中的主键值,定位并读取完整行记录(回表
);
存储引擎把记录交给
Server
层去检测该记录是否满足
where
条件。
在使用
ICP**
的情况下,查询优化阶段会将部分或全部
where
条件下推,其扫描过程如下:
**
存储引擎读取二级索引记录(不是完整行
);
判断当前二级索引列记录是否满足下推的
where**
条件
**
:
如果条件不满足,则跳过该行,继续处理下一行索
引记录;
如果条件满足,
**
则
使用索引中的主键去定位并
读取完整的行记录(回表
)
**
;
存储引擎把记录交给
Server
层,
Server
层检测该记录是否满足
where
条件的其余部分。
适用场景:
·
**
单列
/
多列二级索引上的非范围扫描(比如
like
)
**
。示例:
(c3)
是单列二级索引,
with index condition
部
分就是
ICP
下推的条件
·
where**
条件不满足最左匹配原则的多列二级索引扫描
**
。示例:
(c2, c3, c4)
是多列二级索引,指定多列范围,
c4>5
范围无法下推到引擎层扫描范围
QUICK_RANGE_SELECT::ranges
使用限制
&**
适用条件:
**
当需要访问全表记录时,
ICP
可用于
range
(范围扫描)、
ref
(非唯一索引的
"="
操作)、
eq_ref
(唯一索引
的
"="
操作)
和
ref_or_null
(
ref +
支
持空值,比如:
WHERE col = ... OR col IS NULL
)
访问方法。
2. ICP
可以用于
InnoDB
和
MyISAM
引擎表(包括分区表)。
对于
InnoDB
表,
ICP
仅支持二级索引。而对于
InnoDB
聚簇索引,由于完整的
记录会被读到
InnoDB
缓冲
区,在这种情况下,使用
ICP
不会减少
I/O
操作。
虚拟列上创建的二级索引不支持
ICP
。
使用子查询的
where
条件不支持
ICP
。
由于引擎层无法调用位于
server
层的存储过程,因此,调用存储过程的
SQL
不支持
ICP
。
触发器不支持
ICP
。
开关(默认开启
**
):
**
性能影响:
示例
准备:
启用
ICP
:
禁用
ICP
:
结论
由以上测试情况可以看到,
**
在二级索引是复合索引且前面的条件过滤性较低的情况下,打开
ICP
可以有效的降低
server
层和
engine
层之间交互的次数,从而有效的降低运行时间(从
12.86s
降低到
1.97s
),
但是,对于多个
普通单列索引构成的
where
过滤条件,无论是否启用
ICP
,优化器都会将过滤性高的索引条件
下推到
engine
层
执行
index range scan
,因此,收益不大
**
。
2.
引擎条件下推
ECP
ECP
(
Engine Condition Pushdown
,引擎条件下推
),
该优化只支持
NDB
存储引擎,用于提高非索引列和常量
之间直接比较的效率,在这种情况下,条件被下推到存储引擎做计算。
对于
NDB
集群,这种优化可以消除在
集群的数据节点
和
发出查询的
MySQL
服务器
之间通过网络发送
不匹配的
行的资源浪费。
3.
派生条件下推
DCP
功能介绍
DCP
(
Derived Condition Pushdown
,派生表条件下推
),
从
MySQL 8.0.22**
版本
**
开始引入。
对于
SQL
语句:
在很多情况下可以将外部的
WHERE
条件下推到派生表,相当于
SQL
改写
为了:
这
**
减少了派生表返回的行数
**
,从而加快查询的速度。
适用场景
DCP
适用于以下情况:
1
、当派生表不使用聚合函数或窗口函数时,外部
WHERE
条件可以直接下推给它,包括具有
多个谓词与
AND
、
OR
或与二者同时连接的
WHERE
条件。比如:查询
被重写为
2
、当派生表具有
GROUP BY
且未使用窗口函数时,如果外部
WHERE
条件引用了一个或多个不属于
GROUP BY
的
列,那么该
WHERE
条件可以作为
HAVING
条件下推到派生表中。比如:查询
被重写为
3
、当派生表使用一个
GROUP BY
且外部
WHERE
条件中的列就是
GROUP BY
的列时,引用这些列的
WHERE
条件
可以直接下推到派生表中。比如:查询
被重写为
4
、如果外部
WHERE
条件中同时包含了第
2
种与第
3
种的情况,即同时具有
”
引用属于
GROUP BY
的列的谓词
“
和
”
引用不属于
GROUP BY
的列的谓词
“
,则第一种谓词作为
WHERE
条件下推,第二种谓词下推后作
为
HAVING
条
件。比如:查询
被重写为类似如下形式的
SQL
使用限制
DCP
也存在如下使用限制:
如果派生表包含
UNION
,不能使用
DCP
。该
限制在
MySQL 8.0.29
基本被取消了,但以下两种情况除外:
如果
UNION
的任何派生表是
recursive common table expression
,则不能将条件下推到
UNION
查
询。
不能将
”
包含不确定表达式的条件
“
下推到派生表中。
派生表不能使用
limit
子句。
包含子查询的条件不能被下推。
如果派生表是外部
join
的
inner table
,不能使用
DCP
。
如果派生表是一个
common table expression
并且被多次引用,则不能将条件下推到该派生表。
如果条件的形式是
derived_column > ?
,可以下推使用参数的条件。但是,
If a derived column in an outer WHERE condition is an expression having a ? in the underlying derived table, this condition
cannot be pushed down.
开关(默认开启
**
):
**
4.
谓词下推
何为谓词?
P : X→ {true, false} called a predicate on X .
A predicate is a function that returns bool (or something that can be implicitly converted to bool
)
谓词是返回
bool**
型(或可隐式转换为
bool
型)的函数
**
。
一般来说,
where
中的条件单元都是谓词:
=, >, <, >=, <=, BETWEEN, LIKE, IS [NOT] NULL
<>, IN, OR, NOT IN, NOT LIKE
谓词下推是指将查询语句中的过滤表达式尽可能下推到距离数据源最近的地方做计算,以尽早完成数据的过滤,进
而显著地减少数据传输或计算的开销。
下推前:
select count(1) from t1 A join t3 B on A.a = B.a where A**.b > 100** and B**.b > 1****00**;
下推后:
select count(1) from (select * from t1 where a**>100**) A join (select * from t3 where b**<10****0**) B on A.a = B.a;
MySQL**/**PG
优化器会自动做谓词下推的优化
,比如:
5. Secondary Engine - HTAP
问:该特性是什么版本引入的?
从手册中对
SECONDARY_LOAD
的说明以及代码提交记录时间点,是在
8.0.13
引入的
Secondary Engine
。之
后,
release log
中直到
8.0.19
版本才有相关
bug
修复记录。
在支持
InnoDB
的同时,还可以把数据存放在其他的存储引擎上。
全量的数据都存储
在
Primary Engine
上,某些
指定表数据在
Secondary Engine
上也存放了一份,然后在访问这些数据的时候,会根据系统参数和
cost
选择存储
引擎,提高查询效率。
MySQL
官方集成了
RAPID
来为
MySQL
提供实时的数据分析服务,即
,同时支持
InnoDB
和
RAPID
执
行引擎(未开源
),
也就是
HTAP
。
不过,开源
MySQL
引入
Secondary Engine
机制,有助于集成其他存储引擎或者数据
库,开源生态中
就
是基于该特性来实现的
HTAP
。
第三方方案
本小节只介绍
RDS
范畴的计算下推,对于
PolarDB
、
Aurora
这种存算分离架构不做讲述。
1.
、
sum**
下推
**
腾讯云
TXSQL
(腾讯自研
MySQL
分支)支持了
limit/offset
、
sum
下推。
功能介绍
该功能
**
将单表查询的
LIMIT/OFFSET
或
SUM
操作下推到
**InnoDB
,有效降低查询时延。
LIMIT/OFFSET
下推到二级索引时,该功能将避免
“
回表
”
操作,有效降低扫描代价。
SUM
操作下推到
InnoDB
时,在
InnoDB
层进行计算返回
“
最终
”
结果,节省
Server
层和
InnoDB
引擎层多次
迭代
“
每行
”
记录的代价。
适用场景
该功能主要针对单表查询下存在
LIMIT/OFFSET
或
SUM
的场景,如
Select
*
from tbl
Limit 10,2
、
Select sum(c1) from tbl
等语句。
无法优化的场景:
·
查询语句存在
distinct
、
group by
、
having
。
存在嵌套子查询。
使用了
FULLTEXT
索引。
存在
order by
并且优化器不能利用
index
实现
order by
。
使用多范围的
MRR
。
存在
SQL_CALC_FOUND_ROWS
。
个人分析:
下推的前提是不能影响结果集的正确性
,因此:
只能支持单表查询
。
where
条件:
若无
where
条件,也可支持单表的全表扫描(
Table Scan
)
;
若有
where
条件,则必须满足只对一条索引做范围扫描即可覆盖全部
where
条件才可下推,反之,则
不能下推
。
不支持全文索引这种特殊的索引
。
·
若存在无法被优化器消除的
distinct
、
group by
、
having
、
order by
,则不能下推
。
由于
MRR
机制、
SQL_CALC_FOUND_ROWS
语法的特殊性,
下推的收益不大
。
性能数据
sysbench
导入一百万行数据后:
执行
select * from sbtest1 limit 1000000,1;
的时间从
6.3
秒下降到
2.8
秒。
对于高并发、二级索引扫描且需回表主键列的情况,收益
**
会更大,可能有
8
倍以上的提升
**
。
执行
select sum(k) from sbtest1;
的时间从
5.4
秒下降
到
1.5
秒。
实现机制
无论是
limit**/offset
下推,还是
sum
下推,都借鉴了
ICP
的机制,思路大同小异。这里以
offset
为例,说下我
的理解
**
:
在
Server
层做查询优化时,为了避免下推后导致结果集有误,需先判断是否满足下推条件(单表查询、
InnoDB
引擎、非「无法优化的场景」
),
若满足,则将
offset
条件下推到引擎层,同时屏蔽掉
Server
层的
offset
逻辑。
若下推了
offset
算子,比如
offset 100
,则需要在引擎层跳过
100
行,后续逻辑与下推前相同。
个人对
offset**
下推的理解
**
延伸
问:其他聚合函数是否可以下推优化?
从官方手册(
)支持的聚合函数来看:
至少
AVG()
也是可以很容易支持的。
对于
COUNT()
函数,由于
MySQL 8.0
支持了并行扫描,暂时来看优化的意义不大。
对于
MIN()
、
MAX()
函数,优化器会使用索引来优化,
基本只扫描一行即可,无下推必要。
其他聚合函数不太常用,下推优化意义不大。
参考
MySQL ICP
:
dev.mysql.com/doc/refman/… MySQL ECP
:
dev.mysql.com/doc/refman/…- optimization.html
Secondary Engine
:
腾讯自研内核
TXSQL
计算下推功能:
cloud.tencent.com/document/pr…
作者
卢文双,中国移动云能力中心数据库产品部
- MySQL
内核
研发工程师