【GreatSQL优化器-06】条件过滤导致选择非最佳

news/2025/2/28 20:15:34/文章来源:https://www.cnblogs.com/greatsql/p/18598784

【GreatSQL优化器-06】条件过滤导致选择非最佳

一、condition_fanout_filter导致计划非最佳

GreatSQL 的优化器对于 join 的表需要根据行数和 cost 来确定最后哪张表先执行哪张表后执行,这里面就涉及到预估满足条件的表数据,condition_fanout_filter会根据一系列方法计算出一个数据过滤百分比,这个比百分比就是 filtered 系数,这个值区间在[0,1],值越小代表过滤效果越好。用这个系数乘以总的行数就可以得出最后需要扫描的表行数的数量,可以大幅节省开销和执行时间。

这个功能是由OPTIMIZER_SWITCH_COND_FANOUT_FILTER这个OPTIMIZER_SWITCH来控制的,默认是打开的。因此一般情况下不需要特意去关闭,但是如果遇到执行特别慢的一些情况可以考虑关闭。

下面用一个例子来说明condition_fanout_filter有可能导致选择错误的情况:

# 创建2张表,都只在第二列创建索引,其中t3的最后一列也创建一个索引。
CREATE TABLE t3 (ccc1 INT, ccc2 int,ccc3 datetime(6));
INSERT INTO t3 VALUES (1,2,'2021-03-25 16:44:00.123456'),(2,10,'2021-03-25 16:44:00.123456'),(3,4,'2022-03-25 16:44:00.123456'),(4,6,'2023-03-25 16:44:00.123456'),(null,7,'2024-03-25 16:44:00.123456'),(4,3,'2024-04-25 16:44:00.123456'),(null,8,'2025-03-25 16:44:00.123456'),(3,4,'2022-06-25 16:44:00.123456'),(5,4,'2021-11-25 16:44:00.123456');
CREATE TABLE t4 (d1 INT, d2 int, d3 varchar(100));
INSERT INTO t4 VALUES (1,2,'aa1'),(2,1,'bb1'),(2,3,'cc1'),(3,3,'cc1'),(4,2,'ff1'),(4,4,'ert'),(4,2,'f5fg'),(null,2,'ee'),(5,30,'cc1'),(5,4,'fcc1'),(4,10,'cc1'),(6,4,'ccd1'),(null,1,'fee'),(1,2,'aa1'),(2,1,'bb1'),(2,3,'cc1'),(3,3,'cc1'),(4,2,'ff1'),(4,4,'ert'),(4,2,'f5fg'),(null,2,'ee'),(5,30,'cc1'),(5,4,'fcc1'),(4,10,'cc1'),(6,4,'ccd1'),(null,1,'fee'),(1,2,'aa1'),(2,1,'bb1'),(2,3,'cc1'),(3,3,'cc1'),(4,2,'ff1'),(4,4,'ert'),(4,2,'f5fg'),(null,2,'ee'),(5,30,'cc1'),(5,4,'fcc1'),(4,10,'cc1'),(6,4,'ccd1'),(null,1,'fee');
CREATE INDEX idx3_2 ON t3(ccc2);
CREATE INDEX idx3_3 ON t3(ccc3);
CREATE INDEX idx4_2 ON t4(d2);

执行一个join命令,where条件涉及的列不含t4的索引列,但是包含t3的索引列。
首先查看条件过滤开启的情况,结果是t4先执行全表扫描,预估的扫描行数为39 * 33.33%=13行,而t3执行ref索引扫描,行数为1 * 11.11%=0.1行,总行数为2行

greatsql> EXPLAIN SELECT * FROM t4 join t3 ON t4.d1=t3.ccc1 and t4.d2=t3.ccc2 where t4.d1<5 and t3.ccc3 < '2023-11-15';
+----+-------------+-------+------------+------+---------------+--------+---------+-----------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key    | key_len | ref       | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+--------+---------+-----------+------+----------+-------------+
|  1 | SIMPLE      | t4    | NULL       | ALL  | idx4_2        | NULL   | NULL    | NULL      |   39 |    33.33 | Using where |
|  1 | SIMPLE      | t3    | NULL       | ref  | idx3_2,idx3_3 | idx3_2 | 5       | db1.t4.d2 |    1 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+--------+---------+-----------+------+----------+-------------+

接着查看条件过滤关闭的情况,结果是t3先执行范围扫描,预估的扫描行数为6 * 100%=6行,而t4执行ref索引扫描,行数为6 * 100%=6行,总行数为39行。

greatsql> EXPLAIN SELECT /*+ set_var(optimizer_switch='condition_fanout_filter=off') */ * FROM t4 join t3 ON t4.d1=t3.ccc1 and t4.d2=t3.ccc2 where t4.d1<5 and t3.ccc3 < '2023-11-15';
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key    | key_len | ref         | rows | filtered | Extra                              |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+
|  1 | SIMPLE      | t3    | NULL       | range | idx3_2,idx3_3 | idx3_3 | 9       | NULL        |    6 |   100.00 | Using index condition; Using where |
|  1 | SIMPLE      | t4    | NULL       | ref   | idx4_2        | idx4_2 | 5       | db1.t3.ccc2 |    6 |   100.00 | Using where                        |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+

接下来关掉condition_fanout_filter然后强制用t4 & t3来连接,对比一下计算出来的cost实际达到多少。从下面2个结果可以看出,t4走了全表扫描实际的cost达到21.70,是估计值的2倍多。

greatsql> EXPLAIN FORMAT=TREE SELECT * FROM t4 join t3 ON t4.d1=t3.ccc1 and t4.d2=t3.ccc2 where t4.d1<5 and t3.ccc3 < '2023-11-15';
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                        |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join  (cost=10.00 rows=2)-> Filter: ((t4.d1 < 5) and (t4.d2 is not null))  (cost=4.15 rows=13)-> Table scan on t4  (cost=4.15 rows=39)-> Filter: ((t3.ccc1 = t4.d1) and (t3.ccc3 < TIMESTAMP'2023-11-15 00:00:00'))  (cost=0.32 rows=0.1)-> Index lookup on t3 using idx3_2 (ccc2=t4.d2)  (cost=0.32 rows=1)|
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+greatsql> EXPLAIN FORMAT=TREE SELECT /*+ set_var(optimizer_switch='condition_fanout_filter=off') qb_name(qb1) JOIN_ORDER(@qb1 t4,t3) */ * FROM t4 join t3 ON t4.d1=t3.ccc1 and t4.d2=t3.ccc2 where t4.d1<5 and t3.ccc3 < '2023-11-15';
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                       |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join  (cost=21.70 rows=50)-> Filter: ((t4.d1 < 5) and (t4.d2 is not null))  (cost=4.15 rows=39)-> Table scan on t4  (cost=4.15 rows=39)-> Filter: ((t3.ccc1 = t4.d1) and (t3.ccc3 < TIMESTAMP'2023-11-15 00:00:00'))  (cost=0.32 rows=1)-> Index lookup on t3 using idx3_2 (ccc2=t4.d2)  (cost=0.32 rows=1)|
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

以上例子因为condition_fanout_filter的设置不同而导致选择了不同的驱动表,最后的扫描行为也不一样。但是明显先执行t3的索引范围扫描比t4的全表扫描效率高,因此这个例子可以看出condition_fanout_filter的预估过滤百分比有更多主观性,最终可能导致错误的优化路径。

附表:join_type访问方法的类型

join_type访问方法的类型 说明
JT_UNKNOWN 无效
JT_SYSTEM 表只有一行,比如select * from (select 1)
JT_CONST 表最多只有一行满足,比如WHERE table.pk = 3
JT_EQ_REF =符号用在唯一索引
JT_REF =符号用在非唯一索引
JT_ALL 全表扫描
JT_RANGE 范围扫描
JT_INDEX_SCAN 索引扫描
JT_FT Fulltext索引扫描
JT_REF_OR_NULL 包含null值,比如"WHERE col = ... OR col IS NULL
JT_INDEX_MERGE 一张表执行多次范围扫描最后合并结果

以上各类扫描方式由快到慢排序为:system > const > eq_ref > ref > range > index > ALL

二、不关condition_fanout_filter的解决办法

如果不关闭condition_fanout_filter有没有办法强制指定连接顺序呢?答案是有的。一共如下3个方法,可以按照自己的需要进行灵活操作。

1、使用 qb_name 提示词来指定连接顺序

greatsql> EXPLAIN SELECT /*+ qb_name(qb1) JOIN_ORDER(@qb1 t3,t4) */ * FROM t4 join t3 ON t4.d1=t3.ccc1 and t4.d2=t3.ccc2 where t4.d1<5 and t3.ccc3 < '2023-11-15';
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key    | key_len | ref         | rows | filtered | Extra                              |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+
|  1 | SIMPLE      | t3    | NULL       | range | idx3_2,idx3_3 | idx3_3 | 9       | NULL        |    6 |   100.00 | Using index condition; Using where |
|  1 | SIMPLE      | t4    | NULL       | ref   | idx4_2        | idx4_2 | 5       | db1.t3.ccc2 |    6 |     3.33 | Using where                        |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+

2、在所有 WHERE 条件列上建立索引

greatsql> CREATE INDEX idx4_1 ON t4(d1);greatsql> EXPLAIN SELECT * FROM t4 join t3 ON t4.d1=t3.ccc1 and t4.d2=t3.ccc2 where t4.d1<5 and t3.ccc3 < '2023-11-15';
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key    | key_len | ref         | rows | filtered | Extra                              |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+
|  1 | SIMPLE      | t3    | NULL       | range | idx3_2,idx3_3 | idx3_3 | 9       | NULL        |    6 |   100.00 | Using index condition; Using where |
|  1 | SIMPLE      | t4    | NULL       | ref   | idx4_2,idx4_1 | idx4_1 | 5       | db1.t3.ccc1 |    5 |    16.67 | Using where                        |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+

3、用 JOIN_FIXED_ORDER hint 加上表顺序来强制连接顺序。

greatsql> EXPLAIN SELECT /*+ qb_name(qb1) JOIN_FIXED_ORDER(@qb1) */ * FROM t3 join t4 ON t4.d1=t3.ccc1 AND t4.d2=t3.ccc2 WHERE t4.d1<5 AND t3.ccc3 < '2023-11-15';
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key    | key_len | ref         | rows | filtered | Extra                              |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+
|  1 | SIMPLE      | t3    | NULL       | range | idx3_2,idx3_3 | idx3_3 | 9       | NULL        |    6 |   100.00 | Using index condition; Using where |
|  1 | SIMPLE      | t4    | NULL       | ref   | idx4_2        | idx4_2 | 5       | db1.t3.ccc2 |    6 |     3.33 | Using where                        |
+----+-------------+-------+------------+-------+---------------+--------+---------+-------------+------+----------+------------------------------------+

三、如何排查类似问题

从以上例子看出,打开条件过滤有时并不总是能提高性能,优化器可能会高估条件过滤的影响,个别场景下使用条件过滤反而会导致性能下降。GreatSQL的condition_fanout_filter参数默认是打开的,因此需要自己来判断是否需要这个功能。一般来说,遇到以下场景需要特别注意条件过滤错估的情况。

情况 解决办法
join连接表有大表,并且条件列没有索引 join连接的字段如果没有索引,应当先加上索引,以便优化器能够掌握字段值的分布情况,更准确的预估行数。
join表有特别大的表和小表 判断表的join顺序是否合适,通过改变表的join顺序,让更小的表作为驱动表。可以考虑使用hint,强制优化器使用指定的表join顺序。
运行sql前先使用explain提前查看执行计划,判断条件过滤结果是否合理 如果不使用条件过滤,性能会更好,那么可以关闭会话级条件过滤功能。

四、总结

这节用了一个例子展示了条件过滤误判的情况,知道了打开条件过滤有时并不总是能提高性能,优化器可能会高估条件过滤的影响,个别场景下使用条件过滤反而会导致性能下降。GreatSQL的condition_fanout_filter参数默认是打开的,因此需要自己来判断是否需要这个功能。


Enjoy GreatSQL 😃

关于 GreatSQL

GreatSQL是适用于金融级应用的国内自主开源数据库,具备高性能、高可靠、高易用性、高安全等多个核心特性,可以作为MySQL或Percona Server的可选替换,用于线上生产环境,且完全免费并兼容MySQL或Percona Server。

相关链接: GreatSQL社区 Gitee GitHub Bilibili

GreatSQL社区:

社区博客有奖征稿详情:https://greatsql.cn/thread-100-1-1.html

image-20230105161905827

技术交流群:

微信:扫码添加GreatSQL社区助手微信好友,发送验证信息加群

image-20221030163217640

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/850743.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python异步编程(上):协程和任务

原文链接: https://mp.weixin.qq.com/s/dQOocc7wHaGv7_cf476Ivg 介绍 了解异步编程前先了解一些概念:协程(coroutine)、任务(task)和事件循环(event loop),在3.7以前还需要关心Future这个东西,不过之后提供的高级API弱化了这个概念,你基本不需要关心Future是什么。 协程 协…

ADC_DMA

功能实现:ADC DMA方式循环采样。 采集周期:(256-192)*16*Tsys = (256-192)*16/62.4M = 16.5us 进中断周期:16.5us*采集数据 = 16.5us int main() {uint8_t i;signed short RoughCalib_Value = 0; // ADC粗调偏差值uint32_t temp = 0;uint8_t adcchidx = 0;HSECFG_C…

销售精英的十大核心:解锁成功之路的钥匙

认真 一个秉持认真态度的人,其核心特质在于严谨。这份严谨引领我们向更优方向、更高目标迈进。若在职场上全力以赴,辛劳感将逐渐淡化。掌握这一要诀,便如同握有通往成功之门的金钥匙。 自我驱动力 销售工作因其特性,常面临客户的拒绝与打击,失败远多于成功。自我驱动力不足…

【随手记录】org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length

org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length启动spring boot项目报以上错误,原因是:YAML文件的编码格式导致的 ,可以通过setting调整编码格式,统一为UTF-8:

html页面中如何实现gif图片重新播放?

有几种方法可以实现在 HTML 页面中重新播放 GIF 图片:使用 JavaScript 重新加载 GIF: 这是最常见和最简单的方法。通过操作 GIF 的 src 属性,可以强制浏览器重新加载图像,从而重新开始动画。 function reloadGif(imgElement) {imgElement.src = imgElement.src; }// HTML 中…

剪映6.0.1便携版和vip替换工具

剪映6.0.1便携版和vip替换工具下载 链接: https://pan.baidu.com/s/1yVssYRFO-7Izi3Am9FF6XQ?pwd=360p 提取码: 360p 复制这段内容后打开百度网盘手机App,操作更方便哦 安装方法 把 VECreator.dll 放到 6.0.1.11779 文件夹里

mumu模拟器root

首先去设置里勾选开启root然后使用adb shell进入mumu模拟器控制台,使用su申请root权限,此时mumu模拟器会弹框提示是否允许root,点击是就能开启root权限了

哪些云服务商需要在安装宝塔面板后开放8888端口?

在安装宝塔面板后,许多云服务商需要您手动开放8888端口,否则将无法访问面板页面。以下是一些常见的云服务商及其端口开放方法:阿里云:进入阿里云ECS控制台,选择您的实例。 点击“安全组”选项卡,进入安全组配置页面。 添加一条入方向规则,允许TCP协议的8888端口访问。 保…

网站怎么修改备案号,如何轻松更新网站底部的备案信息

如果您需要修改网站底部的备案信息,可以按照以下步骤进行操作:登录后台管理:使用您的账户信息登录网站的后台管理系统。导航至底部设置:登录后,导航至“模板管理”或“页面管理”等相关页面。这些页面通常会包含底部内容的编辑功能。选择模板文件:在模板管理页面中,找到…

AD常用快捷键及tips

1. 快捷键( → 后面为推荐修改的个人快捷键) S+C 选择整条线 TOL 在矩形区域排列 (通过原理图选中PCB器件时,按shift+ctrl+x,进入交叉选择模式) TM 复位规则 DSD 重新定义板框大小 TVB 定义板切割槽 S+L 线选 S+I 区域框选 EOS 设置原点 EK 裁剪导线 shilt + C 删…

网站favico修改后刷新,如何在修改网站Favicon后确保浏览器显示最新图标

如果您需要修改网站的Favicon并在浏览器中显示最新的图标,可以按照以下步骤进行操作:准备新的Favicon:确保您准备了一个新的Favicon文件,通常是.ico格式。也可以使用.png格式,但需要确保浏览器支持。上传新Favicon:使用FTP客户端(如FileZilla)连接到您的服务器,将新的…

奇奇怪怪的编程语言:Malbolge

Malbolge 除了我们日常使用的Python、Java、C等主流编程语言外,还存在这么一类极为晦涩难懂的编程语言,被称为深奥的编程语言(Esoteric programming language,简称Esolang)。它们被设计用于测试计算机语言表达的极限,所以不会考虑它们的实用性。今天我们来看其中一个非常…