海山数据库(He3DB)MySQL计算下推

158 阅读10分钟

前言

计算下推是数据库优化器优化查询性能的一种常见手段,早期的数据库系统提及的计算下推一般是指谓词下推,其

理论源自关系代数理论。

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

提供实时的数据分析服务,即

HeatWave

,同时支持

InnoDB

RAPID

行引擎(未开源

),

也就是

HTAP

不过,开源

MySQL

引入

Secondary Engine

机制,有助于集成其他存储引擎或者数据

库,开源生态中

StoneDB

是基于该特性来实现的

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**

下推的理解

**

延伸

问:其他聚合函数是否可以下推优化?

从官方手册(

dev.mysql.com/doc/refman/…

)支持的聚合函数来看:

至少

AVG()

也是可以很容易支持的。

对于

COUNT()

函数,由于

MySQL 8.0

支持了并行扫描,暂时来看优化的意义不大。

对于

MIN()

MAX()

函数,优化器会使用索引来优化,

基本只扫描一行即可,无下推必要。

其他聚合函数不太常用,下推优化意义不大。

参考

MySQL ICP

dev.mysql.com/doc/refman/… MySQL ECP

dev.mysql.com/doc/refman/…

MySQL DCP

dev.mysql.com/doc/refman/…- optimization.html

Secondary Engine

mysql.taobao.org/monthly/202…

腾讯自研内核

TXSQL

计算下推功能:

cloud.tencent.com/document/pr…

作者

卢文双,中国移动云能力中心数据库产品部

- MySQL

内核

研发工程师