【GreatSQL优化器-05】条件过滤condition_fanout_filter

SpringBoot+Vue+Mysql社区医院信息平台(源码+文档) SpringBoot+Vue+Mysql学生宿舍信息管理系统 WEB端软件项目(源码+文档) 项目包含:①完整源码(后端+管理后台+用户前台);②数据库脚本文件;③万字论文文档;④开题报告;⑤答辩PPT;⑥项目运行视频教程。 项目架构: B/S 架构、MVC 分层设计 技术栈: Java、MySQL、SpringBoot、MyBatis-Plus、Token 权限校验、管理后台 Vue+ElementUI+ECharts、用户前台 Layui+jQuery、Maven 构建。 一、角色设计 学生用户: 注册登录、浏览宿舍信息、查询宿舍安排、在线报修、缴费查询、失物招领、桶装水预订、评论交流、收藏等。 商家用户: 注册登录、桶装水品牌与商品信息维护、预订订单查看与处理。 维修人员: 注册登录、查看待处理报修、接单维修、填写报修处理结果与进度反馈。 管理员: 学生管理、班级管理、宿舍信息管理、宿舍安排管理、报修处理管理、缴费信息管理、卫生检查与文明宿舍管理、失物招领管理、桶装水与商家管理、维修人员管理、公告资讯与轮播图管理等。 二、前台功能模块(学生端) 1. 首页展示:轮播图、宿舍公告、最新动态推荐、常用服务快速入口 2. 宿舍信息模块:宿舍列表、楼栋与房间条件筛选、宿舍详情查看、我的宿舍安排查询 3. 在线报修模块:在线提交宿舍设施报修申请、上传故障描述、查看报修进度与维修处理结果 4. 缴费模块:水电等缴费记录查询、缴费状态查看 5. 卫生检查与文明宿舍:宿舍卫生检查结果公示、文明宿舍展示 6. 失物招领模块:失物信息发布、招领信息发布、详情查看、评论互动 7. 桶装水预订:桶装水品牌与商品浏览、在线预订、我的预订记录查询 8. 假期留宿:假期留校申请提交、申请记录查询 9. 评论与收藏:信息评论、查看他人评论、评论回复互动、收藏感兴趣的内容 10. 个人中心... 立即下载

【GreatSQL优化器-05】条件过滤condition_fanout_filter

一、condition_fanout_filter介绍

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

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

下面用一个简单的例子来说明 condition_fanout_filter 是什么:

CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 INT,date1 DATETIME);
INSERT INTO t1 VALUES (1,10,'2021-03-25 16:44:00.123456'),(2,1,'2022-03-26 16:44:00.123456'),(3,4,'2023-03-27 16:44:00.123456'),(5,5,'2024-03-25 16:44:00.123456');
CREATE TABLE t2 (cc1 INT PRIMARY KEY, cc2 INT);
INSERT INTO t2 VALUES (1,3),(2,1),(3,2),(4,3),(5,15);
# 为了查看过滤系数,需要创建一张没有主键的表用来做过滤估计。
CREATE TABLE t3 (ccc1 INT, ccc2 varchar(100));
INSERT INTO t3 VALUES (1,'aa1'),(2,'bb1'),(3,'cc1'),(4,'dd1'),(null,'ee');
CREATE INDEX idx1 ON t1(c2);
CREATE INDEX idx2 ON t1(c2,date1);
CREATE INDEX idx2_1 ON t2(cc2);
CREATE INDEX idx3_1 ON t3(ccc1);

看到下面的 t3 的过滤百分比46.66%,意味着两张表 join 一共20行,因为 t3 的过滤百分比为 46.66%,因此最后只需要读取 20*46.66%=9 行数据。

注意,这里没有建立直方图,因此结果不包含直方图过滤的因素,关于直方图后面会专门开一章讲。

greatsql> EXPLAIN SELECT * FROM t1 JOIN t3 ON t1.c1=t3.ccc1 OR t3.ccc1 <3;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | index | PRIMARY       | idx2 | 11      | NULL |    4 |   100.00 | Using index                                |
|  1 | SIMPLE      | t3    | NULL       | ALL   | idx3_1        | NULL | NULL    | NULL |    5 |    46.66 | Using where; Using join buffer (hash join) |
# 这里显示的值就是 t3 的过滤数据百分比
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------------+

greatsql> EXPLAIN FORMAT=TREE SELECT * FROM t1 JOIN t3 ON t1.c1=t3.ccc1 OR t3.ccc1<3;
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                       |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: ((t3.ccc1 = t1.c1) or (t3.ccc1 < 3))  (cost=3.65 rows=9)
    -> Inner hash join (no condition)  (cost=3.65 rows=9) # 这里结果为读取9行数据,跟上面算出来的数据一致
        -> Table scan on t3  (cost=0.12 rows=5)
        -> Hash
            -> Index scan on t1 using idx2  (cost=1.40 rows=4)
 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
二、best_access_path代码解释

condition_fanout_filte的计算在 best_access_path函数实现,用来预估不同 join 连接时候的 join 表的读取行数和可能的 cost。

void Optimize_table_order::best_access_path(JOIN_TAB *tab,
                                            const table_map remaining_tables,
                                            const uint idx, bool disable_jbuf,
                                            const double prefix_rowcount,
                                            POSITION *pos) {
# 如果根据前面的结果keyuse_array数组有值的话,那么根据find_best_ref()函数先找出最优索引,按照索引的方式计算cost
  if (tab->keyuse() != nullptr &&
      (table->file->ha_table_flags() & HA_NO_INDEX_ACCESS) == 0)
    best_ref =
        find_best_ref(tab, remaining_tables, idx, prefix_rowcount,
                      &found_condition, &ref_depend_map, &used_key_parts);
  # 最主要计算下面3个值
  pos->filter_effect = filter_effect = std::  (1.0, tab->found_records * calculate_condition_filter() / rows_after_filtering);
  pos->rows_fetched = rows_fetched = rows_after_filtering;
  pos->read_cost = scan_read_cost = calculate_scan_cost();    
}

下面是代码里面涉及的计算公式,这里是 keyuse_array 数组为空的情况,也就是扫描方式 "access_type" 非 "eq_ref" 和 "ref" 的情况,或者说是没有找到最优索引的情况。keyuse_array 数组有值的情况,在函数find_best_ref()计算,结果有可能也走下面的计算方式,详细在后面的章节详细介绍。

关键参数解释值
rows_fetched总共需要读取多少行rows_after_filtering 见下表一
filter_effect条件过滤百分比系数std::min(1.0, tab->found_records * calculate_condition_filter() / rows_after_filtering) 见下表一和二
read_cost读的开销calculate_scan_cost() 见下表四
prefix_rowcountjoin左表的行数,意味着多少行会被join到右表 对于第一张表来说prefix_rowcount=1第一张表:prefix_rowcount = rows_fetched * filter_effect 非第一张表:prefix_rowcount = 上一张表的prefix_rowcount * rows_fetched * filter_effect
prefix_costjoin左表的cost,row_evaluate_cost()计算公式=0.1 * 行数,0.1是读一行的开销第一张表:read_cost + cm->row_evaluate_cost(prefix_rowcount) 非第一张表:上一张表的prefix_cost + read_cost + cm->row_evaluate_cost(上一张表的prefix_rowcount*rows_fetched)

表一,rows_after_filtering 计算方式

场景解释值
OPTIMIZER_SWITCH_COND_FANOUT_FILTER模式(默认ON)条件过滤模式开启tab->found_records * calculate_condition_filter() 见下表二
table->quick_condition_rows != tab->found_records通过别的方法获取的表满足的行数table->quick_condition_rows
keyuse_array有值(参考前面<<优化器02>>)REF扫描模式tab->found_records * 0.75
以上都不符合。默认情况全表行数tab->found_records

表二,calculate_condition_filter() 计算方式

场景值说明
满足条件的表的行数为0filter = COND_FILTER_ALLPASS 见表三
使用索引filter = filter * std::min(table->quick_rows[keyno] / tab->records() , 1.0)
带有条件并且条件里涉及不含有索引的列filter = filter * Item_cond->get_filtering_effect() 见表三
合并结果filter = max(filter, 1.0 / tab->records())
(filter * fanout) < 0.05filter = 0.05 / fanoutfanout是满足条件的表的行数

注:filter是条件过滤百分比,这个值区间在[0,1],这个值越小代表过滤效果越好

表三,get_filtering_effect() 算出来的过滤系数

Item的系数解释系数说明
COND_FILTER_ALLPASSAlways true1.0f代表全部数据都符合,全部都要扫描
COND_FILTER_EQUALITYcol1 = col20.1f代表预估10%数据符合条件
COND_FILTER_INEQUALITYcol1 > col20.3333f代表预估1/3数据符合条件
COND_FILTER_BETWEENcol1 BETWEEN a AND b0.1111f代表预估1/9数据符合条件

表四,calculate_scan_cost() 计算方式

场景值说明
如果是范围扫描prefix_rowcount * (tab->range_scan()->cost + cost_model->row_evaluate_cost(tab->found_records - *rows_after_filtering))
非join buffer模式prefix_rowcount * (single_scan_read_cost + cost_model->row_evaluate_cost(tab->records() - *rows_after_filtering)) single_scan_read_cost计算如下: force_index模式: table->file->read_cost(tab->ref().key, 1,tab->records() 见表五) 非force_index模式: table->file->table_scan_cost() 见表五
join buffer模式(默认ON)buffer_count * (single_scan_read_cost+ cost_model->row_evaluate_cost(tab->records() - *rows_after_filtering)) buffer_count计算如下: single_scan_read_cost计算如下: 1.0 + ((double)cache_record_length(join, idx) * prefix_rowcount / thd->variables.join_buff_size force_index模式: table->file->read_cost(tab->ref().key, 1,tab->records()) 见表五 非force_index模式: table->file->table_scan_cost() 见表五默认这个模式打开

表五 引擎计算相关

场景值说明
table->file->read_cost(index, ranges, rows)read_time(index, ranges, rows) *table->cost_model()->page_read_cost(1.0)index:index序号,ranges:range范围,rows:表行数
table->file->table_scan_cost()(stats.data_file_length / IO_SIZE + 2) * table->cost_model()->page_read_cost(1.0)IO_SIZE=4096
三、实际例子说明

接下来看一个例子来说明上面的代码。这里的例子不涉及keyuse_array数组有值的情况。

greatsql> SELECT * FROM t1 join t3 ON t1.c1=t3.ccc1 or t3.ccc1<3;
+----+------+---------------------+------+------+
| c1 | c2   | date1               | ccc1 | ccc2 |
+----+------+---------------------+------+------+
|  1 |   10 | 2021-03-25 16:44:00 |    1 | aa1  |
|  5 |    5 | 2024-03-25 16:44:00 |    1 | aa1  |
|  3 |    4 | 2023-03-27 16:44:00 |    1 | aa1  |
|  2 |    1 | 2022-03-26 16:44:00 |    1 | aa1  |
|  1 |   10 | 2021-03-25 16:44:00 |    2 | bb1  |
|  5 |    5 | 2024-03-25 16:44:00 |    2 | bb1  |
|  3 |    4 | 2023-03-27 16:44:00 |    2 | bb1  |
|  2 |    1 | 2022-03-26 16:44:00 |    2 | bb1  |
|  3 |    4 | 2023-03-27 16:44:00 |    3 | cc1  |
+----+------+---------------------+------+------+

greatsql> EXPLAIN FORMAT=TREE SELECT * FROM t1 join t3 ON t1.c1=t3.ccc1 or t3.ccc1<3;
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                       |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: ((t3.ccc1 = t1.c1) or (t3.ccc1 < 3))  (cost=3.65 rows=9)
    -> Inner hash join (no condition)  (cost=3.65 rows=9)
        -> Table scan on t3  (cost=0.12 rows=5)
        -> Hash
            -> Index scan on t1 using idx2  (cost=1.40 rows=4)
 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

greatsql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ],
                "table": "`t1`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 4,
                      "filtering_effect": [
                      ],
                      "final_filtering_effect": 1,
                      "access_type": "scan",
                      "resulting_rows": 4,# 这个值就是rows_after_filtering
                      "cost": 0.65, # 计算公式=read_cost(0.25)+cost_model->row_evaluate_cost(1 * rows_after_filtering)
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100, # 这个值就是filter_effect * 100
                "rows_for_plan": 4, # 这个值就是prefix_rowcount=rows_to_scan * filter_effect / 100
                "cost_for_plan": 0.65, # 这个值就是prefix_cost=read_cost(0.25) + cm->row_evaluate_cost(prefix_rowcount=4)
                "rest_of_plan": [
                  {
                    "plan_prefix": [ # 当前左连接表为t1
                      "`t1`"
                    ],
                    "table": "`t3`", # 右连接表为t3
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "rows_to_scan": 5,
                          "filtering_effect": [
                          ],
                          "final_filtering_effect": 1,
                          "access_type": "scan",
                          "using_join_cache": true,
                          "buffers_needed": 1,
                          "resulting_rows": 5, # 这个值就是rows_after_filtering
                          "cost": 2.25005, # 计算公式read_cost(0.25)+cost_model->row_evaluate_cost(prefix_rowcount * rows_after_filtering=4 * 5)
                          "chosen": true
                        }
                      ]
                    },
                    "condition_filtering_pct": 46.664, # 这个值计算过程见下面<<附录:t3的filter_effect值计算>> ※bug?用explain看到的和直接敲命令看到的不一样,前者46.664后者为100,而"rows_for_plan"会变为没有过滤的20
                    "rows_for_plan": 9.3328, # 这个值就是prefix_rowcount=4(上一张表的prefix_rowcount) * 5 * 46.664 / 100
                    "cost_for_plan": 2.90005, # 这个值就是prefix_cost=0.65(上一张表的prefix_cost)+read_cost(0.25) + cm->row_evaluate_cost(20=4*5)
                    "chosen": true
                  }
                ]
              },
              {
                "plan_prefix": [
                ],
                "table": "`t3`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 5,
                      "filtering_effect": [
                      ],
                      "final_filtering_effect": 1,
                      "access_type": "scan",
                      "resulting_rows": 5,
                      "cost": 0.75, # 计算公式read_cost(0.25)+5行*0.1
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 5, # 这个值就是prefix_rowcount
                "cost_for_plan": 0.75, # 这个值就是prefix_cost
                "rest_of_plan": [
                  {
                    "plan_prefix": [ # 当前左连接表为t3
                      "`t3`"
                    ],
                    "table": "`t1`", # 右连接表为t1
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "rows_to_scan": 4,
                          "filtering_effect": [
                          ],
                          "final_filtering_effect": 1,
                          "access_type": "scan",
                          "using_join_cache": true,
                          "buffers_needed": 1,
                          "resulting_rows": 4,
                          "cost": 3.00776, # 计算公式read_cost(1.007)+cost_model->row_evaluate_cost(prefix_rowcount * rows_after_filtering=5 * 4)
                          "chosen": true
                        }
                      ]
                    },
                    "condition_filtering_pct": 100,
                    "rows_for_plan": 20, # 这个值就是prefix_rowcount=5(上一张表的prefix_rowcount) * 4 * 100 / 100
                    "cost_for_plan": 3.75776, # 这个值就是prefix_cost=0.75(上一张表的prefix_cost)+read_cost(1.007) + cm->row_evaluate_cost(20=5*4)
                    "pruned_by_cost": true # 因为这里算出来的3.75776 > 2.90005,因此被裁剪掉了
                  }
                ]
              }
            ]
          },
          {
            "attaching_conditions_to_tables": { # 添加一些附加条件
              "original_condition": "((`t3`.`ccc1` = `t1`.`c1`) or (`t3`.`ccc1` < 3))",
              "attached_conditions_computation": [
              ],
              "attached_conditions_summary": [ # t1作为驱动表,执行全表扫描,因此不需要任何条件
                {
                  "table": "`t1`",
                  "attached": null
                },
                {
                  "table": "`t3`",
                  "attached": "((`t3`.`ccc1` = `t1`.`c1`) or (`t3`.`ccc1` < 3))" #  t3作为连接表,按照条件过滤
                }
              ]
            }
          },
          {
            "finalizing_table_conditions": [
              {
                "table": "`t3`",
                "original_table_condition": "((`t3`.`ccc1` = `t1`.`c1`) or (`t3`.`ccc1` < 3))",
                "final_table_condition   ": "((`t3`.`ccc1` = `t1`.`c1`) or (`t3`.`ccc1` < 3))"
              }
            ]
          },
          {
            "refine_plan": [
              {
                "table": "`t1`"
              },
              {
                "table": "`t3`"
              }
            ]
          }
        ]
      }
    },
    {
      "join_explain": {
        "select#": 1,
        "steps": [
        ]
      }
    }
  ]
} |           

# 附录:t3的filter_effect值计算
# 这个函数计算t1.c1=t3.ccc1和t3.ccc1<3的filter_effect值,计算过程见下面
float Item_cond_or::get_filtering_effect(THD *thd, table_map filter_for_table,
                                         table_map read_tables,
                                         const MY_BITMAP *fields_to_ignore,
                                         double rows_in_table) {
  while ((item = it++)) {
    const float cur_filter = item->get_filtering_effect(
        thd, filter_for_table, read_tables, fields_to_ignore, rows_in_table);
    # 第一次:计算t1.c1=t3.ccc1,返回 1/5=20%,其中5是总行数。
    # 第二次:计算t3.ccc1<3,返回COND_FILTER_INEQUALITY=0.333
    # 最后的filter=0.2+0.333 - (0.2*0.333)=0.4664
    filter = filter + cur_filter - (filter * cur_filter);
  }
  return filter;
}

下面把OPTIMIZER_SWITCH_COND_FANOUT_FILTER模式关闭看看执行效果什么变化

greatsql> explain SELECT /*+ set_var(optimizer_switch='condition_fanout_filter=off') */ * FROM t1 join t3 ON t1.c1=t3.ccc1 or t3.ccc1<3;
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | index | PRIMARY       | idx2 | 11      | NULL |    4 |   100.00 | Using index                                |
|  1 | SIMPLE      | t3    | NULL       | ALL   | idx3_1        | NULL | NULL    | NULL |    5 |   100.00 | Using where; Using join buffer (hash join) |
                                                                                            # 看到这里选择了不做条件过滤,全表数据连接
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------------+

greatsql> EXPLAIN FORMAT=TREE SELECT /*+ set_var(optimizer_switch='condition_fanout_filter=off') */ * FROM t1 join t3 ON t1.c1=t3.ccc1 or t3.ccc1<3;
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                         |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Filter: ((t3.ccc1 = t1.c1) or (t3.ccc1 < 3))  (cost=3.65 rows=20)# 因为表里数据少,跟刚才比cost没有变化,但是要是数据大的话就很明显
    -> Inner hash join (no condition)  (cost=3.65 rows=20) # 这里变为了20行,意味着读取全部行数据
        -> Table scan on t3  (cost=0.19 rows=5)
        -> Hash
            -> Index scan on t1 using idx2  (cost=1.40 rows=4)
 |
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ],
                "table": "`t1`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 4,
                      "access_type": "scan",
                      "resulting_rows": 4,
                      "cost": 1.4,
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 4,
                "cost_for_plan": 1.4,
                "rest_of_plan": [
                  {
                    "plan_prefix": [ # 当前左连接表为t1
                      "`t1`"
                    ],
                    "table": "`t3`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "rows_to_scan": 5,
                          "access_type": "scan",
                          "using_join_cache": true,
                          "buffers_needed": 1,
                          "resulting_rows": 5,
                          "cost": 2.25005,
                          "chosen": true
                        }
                      ]
                    },
                    "condition_filtering_pct": 100, # 这里的过滤系数变为100,说明不进行条件过滤,读取全部行
                    "rows_for_plan": 20, # 由上面的9行变为全部数据连接,20行数据
                    "cost_for_plan": 3.65005, 
                    "chosen": true
                  }
                ]
              },
              {
                "plan_prefix": [
                ],
                "table": "`t3`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 5,
                      "access_type": "scan",
                      "resulting_rows": 5,
                      "cost": 0.75,
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 5,
                "cost_for_plan": 0.75,
                "rest_of_plan": [
                  {
                    "plan_prefix": [
                      "`t3`"
                    ],
                    "table": "`t1`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "rows_to_scan": 4,
                          "access_type": "scan",
                          "using_join_cache": true,
                          "buffers_needed": 1,
                          "resulting_rows": 4,
                          "cost": 3.00776,
                          "chosen": true
                        }
                      ]
                    },
                    "condition_filtering_pct": 100,
                    "rows_for_plan": 20,
                    "cost_for_plan": 3.75776,
                    "pruned_by_cost": true
                  }
                ]
              }
            ]
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`t3`.`ccc1` = `t1`.`c1`) or (`t3`.`ccc1` < 3))",
              "attached_conditions_computation": [
              ],
              "attached_conditions_summary": [
                {
                  "table": "`t1`",
                  "attached": null
                },
                {
                  "table": "`t3`",
                  "attached": "((`t3`.`ccc1` = `t1`.`c1`) or (`t3`.`ccc1` < 3))"
                }
              ]
            }
          },
          {
            "finalizing_table_conditions": [
              {
                "table": "`t3`",
                "original_table_condition": "((`t3`.`ccc1` = `t1`.`c1`) or (`t3`.`ccc1` < 3))",
                "final_table_condition   ": "((`t3`.`ccc1` = `t1`.`c1`) or (`t3`.`ccc1` < 3))"
              }
            ]
          },
          {
            "refine_plan": [
              {
                "table": "`t1`"
              },
              {
                "table": "`t3`"
              }
            ]
          }
        ]
      }
    },
    {
      "join_explain": {
        "select#": 1,
        "steps": [
        ]
      }
    }
  ]
}  

现在把之前03的内容拿过来回顾一下当时的结果,看看为何是当时的结果。从下面结果可以看出,当存在keyuse的时候,优化器最后走哪条索引是通过find_best_ref()函数来决定的。

greatsql> EXPLAIN SELECT * FROM t1 join t2 ON t1.c1=t2.cc1 and t1.c1<5;
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref       | rows | filtered | Extra       |
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | range  | PRIMARY       | PRIMARY | 4       | NULL      |    3 |   100.00 | Using where |
|  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | db1.t1.c1 |    1 |   100.00 | NULL        |
                                   # t2这个结果看下面的解释
+----+-------------+-------+------------+--------+---------------+---------+---------+-----------+------+----------+-------------+

          {\
            "considered_execution_plans": [\
              {\
                "plan_prefix": [\
                ],\
                "table": "`t1`",\
                "best_access_path": {\
                  "considered_access_paths": [\
                    {\
                      "access_type": "ref",\ # t1表不选择"ref"方式,因为cost比"range"方式大
                      "index": "PRIMARY",\
                      "usable": false,\
                      "chosen": false\
                    },\
                    {\
                      "rows_to_scan": 3,\
                      "filtering_effect": [\
                      ],\
                      "final_filtering_effect": 1,\
                      "access_type": "range",\ # t1表最后选择了"range"方式进行扫描
                      "range_details": {\
                        "used_index": "PRIMARY"\
                      },\
                      "resulting_rows": 3,\
                      "cost": 0.860732,\
                      "chosen": true\
                    }\
                  ]\
                },\
                "condition_filtering_pct": 100,\
                "rows_for_plan": 3,\
                "cost_for_plan": 0.860732,\
                "rest_of_plan": [\
                "rest_of_plan": [\
                  {\
                    "plan_prefix": [\
                      "`t1`"\
                    ],\
                    "table": "`t2`",\
                    "best_access_path": {\
                      "considered_access_paths": [\
                        {\
                          "access_type": "eq_ref",\ # 在t1 join t2的连接方式里,判断用"eq_ref"方式扫描cost更小。这里通过find_best_ref()获取
                          "index": "PRIMARY",\
                          "rows": 1,\
                          "cost": 1.05,\
                          "chosen": true,\
                          "cause": "clustered_pk_chosen_by_heuristics"\
                        },\
                        {\
                          "access_type": "range",\
                          "range_details": {\
                            "used_index": "PRIMARY"\
                          },\
                          "chosen": false,\
                          "cause": "heuristic_index_cheaper"\
                        }\
                      ]\
                    },\
                    "condition_filtering_pct": 100,\
                    "rows_for_plan": 3,\
                    "cost_for_plan": 1.91073,\
                    "chosen": true\
                  }\
          {\
            "refine_plan": [\ # 根据最后比较结果,按照t1,t2方式连接cost最小,因此选择这种顺序进行join
              {\
                "table": "`t1`"\
              },\
              {\
                "table": "`t2`"\
              }\
            ]\
          }\
四、总结

从上面优化器最早的步骤我们认识了条件过滤condition_fanout_filter的定义以及使用方法,也学会了计算join时候的cost,然后根据cost选择最优的连接顺序。回到开头提到的这个功能在一些场景有可能会带来选择错误的问题,这个下一期展开细讲。用这种过滤方法算出来的结果也不精确,GreatSQL 用了另一个功能直方图来做数据统计,让结果更加精确,这个直方图的分析放后面章节讲。

Enjoy GreatSQL :)

关于 GreatSQL

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

相关链接: GreatSQL社区 Gitee GitHub Bilibili

GreatSQL社区:

image

社区有奖建议反馈: https://greatsql.cn/thread-54-1-1.html

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

(对文章有疑问或者有独到见解都可以去社区官网提出或分享哦~)

技术交流群:

微信&QQ群:

QQ群:533341697

微信群:添加GreatSQL社区助手(微信号:wanlidbc )好友,待社区助手拉您进群。

YOLOv8作物病害检测实战源码 这是一套基于YOLOv8n的农田病虫害目标检测系统,对叶片照片做目标检测,识别健康叶片、褐斑病、白粉病、锈病四类,并给出对应防治建议。全流程纯CPU可跑,配套交付完整Python源码、9章实战教程(md/html/pdf三种格式)、100张自造样本及YOLO标签、训练曲线与混淆矩阵截图、Tkinter界面和答辩PPT。 系统围绕五项功能展开:同一套推理逻辑覆盖图像、视频、摄像头三种来源;图形界面选文件即检测,画面叠彩色框标注病名与置信度,右侧面板同步给出症状与防治措施;检测记录自动写入CSV,统计页一键生成四类病害占比饼图;训练结束自动产出损失与精度曲线;内置防治建议文案库,检测命中即回填面板。 选型用YOLOv8n nano档,参数量约320万、权重约6.2MB,CPU单帧几十毫秒,小样本训练5到8分钟完成。数据由data_gen.py程序化绘制100张叶片图,固定随机种子,重跑分布一致,不依赖公开数据集。依赖全部锁版本,教程第7章整理了19条真实报错及排查方法,第8章给出换模型档、换PlantVillage数据集的迁移路径。 适合做计算机视觉方向课程设计或毕业设计的学生,也适合智慧农业方向想了解检测模型如何落进业务面板的学习者。不需要GPU,不需要系统学过目标检测,教程第3章会按需补齐原理。 使用时先pip install -r requirements.txt装好依赖,download_data.py下载预训练权重,data_gen.py生成样本后train.py训练,完成后用main.py infer批量检测或main.py gui启动界面。 立即下载

相关推荐

数据挖掘实战:决策树 + 聚类 + 关联规则,零 Key 离线可跑(Python 源码)

数据挖掘实战包。用 Python 标准库实现 CART 风格决策树、K-Means 聚类与 Apriori 关联规则,在虚构「星河便利店」会员表和购物篮上跑通分类、分群与购物篮分析,并给出准确率、轮廓系数、支持度/置信度/提升度。零第三方依赖、零 API Key 即可离线跑通。含 4 个样例、离线冒烟、中文 README(含「使用方法」)。均为作者原创可运行源码。MIT。作者 blog.youkuaiyun.com/mtl1994

MySQL性能优化-条件过滤(Condition Filtering)

MySQL在处理join查询时,遍历驱动表的记录,把驱动表的记录传递给被驱动表,然后根据join连接条件进行匹配。优化器通常会将更小的表作为驱动表,通过在驱动表上做额外的where条件过滤(Condition Filtering),能够将驱动表限制在一个更小的范围,以便优化器能够做出更优的执行计划。 原文地址: https://mytecdb.com/blogDetail.php?id=97 ...

分享技术,分享知识 1861

科技中介服务机构在技术供需对接中如何实现服务流程的自动化与标准化?.docx

科技中介服务机构在技术供需对接中如何实现服务流程的自动化与标准化?

mysql代价模型_mysql 5.7 代价模型浅析

代价模型mysql 5.7代价计算相对之前的版本有较大的改进。例如代价模型参数可以动态配置,可以适应不同的硬件区分考虑数据在内存和在磁盘中的代价代价精度提升为浮点型jion计算时不仅要考虑condition,还要考虑condition上的filter,具体参见参数condition_fanout_filter5.7 在代价类型上分为io,cpu和memory, 5.7的代价模型还在完善中,memo...

weixin_36143628的博客 396

产业园区如何高效对接企业技术需求与高校科研成果?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

国央企创新负责人如何快速掌握本地科技资源分布与匹配情况?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

mysql optimizer_switch : 查询优化器优化策略深入解析

当查询条件可以通过多个索引来满足时,MySQL 可以合并这些索引以更有效地检索数据。在复杂查询中,这可以显著提高性能。ICP 允许将 WHERE 子句中的条件推送到存储引擎层进行处理。这减少了存储引擎需要返回给优化器的数据量,因为它可以在检索数据时就过滤掉不符合条件的行。当查询包含子查询时,标志控制是否将子查询的结果物化(即临时存储)。物化子查询可以减少重复计算,但也可能增加内存使用。semijoin和loosescan这两个标志与半连接优化相关。

码到三十五 1万+

国央企创新负责人如何实现科技成果的精准识别与高效筛选?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

sql速度优化多条合并为一条语句

在上述代码中,通过 CASE 语句对不同的产品类别进行判断,如果是特定类别(如“A”或“B”),则将对应的销售数量累加到总和中,否则为 0。最后使用 SUM 函数对满足条件的销售数量进行求和,并通过 group('category') 按照产品类别进行分组。例如,假设有一个销售数据表,包含字段 product_id (产品 ID)、 quantity (销售数量)和 category (产品类别)。现在要根据产品类别计算不同类别产品的销售总量,并对特定类别进行特殊处理。

cybersnow精通 28 门计算机语言,凭借其超凡的技术能力,成功开发过上万个应用,广泛涉及政府、商业、个人等众多领域,甚至在检察院、环保局、公安局等专业场景中也大放异彩。不仅熟练掌握单片机和物联网开发,在软件架构设计方面更是独树一帜,自创了跨平台软件 724

高校技术转移办公室人员如何系统化管理科技成果信息?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

网课、网页视频倍速播放简单脚本

一个简单的网页视频倍速脚本

huazn612的博客 1万+

MySQL-5.7-8.2.1.12 Condition Filtering

In join processing, prefix rows are those rows passed from one table in a join to the next. In general, the optimizer attempts to put tables with low prefix counts early in the join order to keep the number of row combinations from increasing rapidly. 在连接

tangwenqiang177的专栏 274

远程桌面连接失败?手把手教你解决CredSSP加密Oracle漏洞(Win10/Win11通用)

本文详细解析了Windows远程桌面连接因CredSSP加密Oracle漏洞安全更新导致的“身份验证错误”问题。文章从原理入手,提供了通过组策略编辑器(适用于专业版)和注册表编辑器(适用于家庭版)两种核心解决方案,指导用户将安全策略调整为推荐的“缓解”模式,以在保障安全的前提下恢复连接,并给出了高级脚本处理与安全最佳实践。

u6v7w8x的博客 724

MySQL 8.0 官方文档 第八章 优化(十四)—— 条件过滤

目录第八章 优化(十四)—— 条件过滤8.2 优化SQL语句8.2.1 优化 SELECT 语句8.2.1.13 条件过滤 第八章 优化(十四)—— 条件过滤 8.2 优化SQL语句 8.2.1 优化 SELECT 语句 8.2.1.13 条件过滤 在连接处理中,前缀行是那些在连接中从一个表传递到下一个表的行。通常,优化器会尝试在连接顺序的早期放置前缀行数较低的表,以防止行组合的数量迅速增加。如果优化器可以使用从一个表中选择并传递给下一个表的行上的条件信息,那么它就可以更准确地计算出行数的估计值,并选择最佳

HZ_Samuel博客 673

UA-DETRAC数据集转YOLO格式

UA-DETRAC转YOLO

lafsca5的博客 9107

离线安装PlutoSDR硬件支持包全攻略:没有MathWorks账号也能搞定MATLAB集成

本文详细介绍了在没有MathWorks账号和网络环境的情况下,如何离线安装PlutoSDR硬件支持包并实现MATLAB集成的完整解决方案。从驱动安装、设备识别到MATLAB支持包的离线部署,提供了全面的步骤指导和常见问题排查方法,特别适合高校实验室、军工单位等内网环境使用。

js777的博客 267

Filter Conditions 过滤条件

<pre name="code" class="html">Filter Conditions 过滤条件: Rsyslog 提供4种不同类型的"filter 条件" "传统的" severity and facility 基于selectors: 基于属性的过滤器 基于表达式的过滤 Selectors 选择器 选择器是过滤消息传统的方式, 它们被保存在 rsyslo...

weixin_30908649的博客 787

全网最详细Seurat v5 流程复现(拿来即用)

本文是基于seurat v5版本的教程复现,主要依据来源于官网seurat v5建议大家有时间的话可以去官网进行详细阅读学习!本节内容在之前seurat v4的教程都很多都有提到,文章所用到的数据及代码已经给各位放在这里了,有需要自取!请点击这里哦提取码:bui1。

ZxVSaccount的博客 1万+

mysql自我优化-优化select语句(四)

mysql自我优化1、外连接优化 首先这里要简要说明,该文章翻译自mysql手册,并经过我的整合,所以文字内容较多,但是实际含金量比较高,建议大家仔细阅读,必有收获。 1、外连接优化

zhuifengshaonian201的博客 407

《Python基础教程》专栏总结篇

今天给大家带来的文章是《Python基础教程》专栏总结篇,希望能对同学们有所帮助。 文章目录 1. 背景 2. 专栏亮点 3. 你的收获 4. 详细目录

weixin_43178406的博客 73万+

国家基本比例尺分幅土地利用现状图制作

本次主要解决的问题是如何构建脚本快速生成所涉及区域标准国家基本比例尺分幅,然后制作土地利用现状图模板,套用模板生成标准分幅土地利用现状图。

xyt1172228177的博客 2458

MySQL 5.7 cost模型浅析

代价模型 mysql 5.7.10 cost计算相对之前的版本有5.7 代价模型浅析较大的改进。例如 cost模型参数可以动态配置,可以适应不同的硬件 区分考虑数据在内存和在磁盘中的cost cost精度提升为浮点型 jion计算时不仅要考虑condition,还要考虑condition上的filter,具体参见参数condition_fanout_filter 5.7 在cost类型上...

oraly的博客 2593

Python 神经计算实训 实训答案分享

【代码】Python 神经计算实训 实训答案分享。

2403_89314916的博客 350

MySQL优化器功能开关optimizer_switch之功能概览

MySQL 8.0新增特性use_invisible_indexes 是否使用不可见索引,MySQL 8.0新增可以创建invisible索引,这一开关控制优化器是否使用invisible索引,on表示考虑使用。MySQL 5.7新增derived_merge 派生表合并,类似Oracle的视图合并,当派生SQL中存在以下操作是无法展开UNION 、GROUP 、DISTINCT、LIMIT及聚合...

oraly的博客 5324

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

美化查看MySQL多选项参数

--美化MySQL多参数查看: MySQL中的有些参数选项很多比如optimizer_switch,查询的时候屏幕有多宽则可以展示多长,非常不利于查看。 正常的查询如下所示: mysql&gt; show variables like 'optimizer_switch'\G *************************** 1. row **************************...

vkingnew 的技术博客 1516

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

15.MySQL优化Condition Filtering

介绍 在连接处理中,前缀行是从连接中的一个表传递到下一个表的那些行。通常,优化器尝试在连接顺序的早期放置具有低前缀计数的表,以保持行组合的数量不会快速增加。在某种程度上,优化器可以利用一些条件信息查询表中需要的行传递给下一个,它可以更准确地计算行估计并选择最佳执行计划。 假如没有条件过滤,表的前缀行数是基于各种优化器的访问方法根据Where条件进行的估量估计。Condition filtering...

ciqingloveless的博客 652

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

上一篇: GreatSQL内存消耗异常排查攻略:从系统到应用层面的深入分析
下一篇: DML操作报列不存在?
GreatSQL社区
博客等级 码龄5年 1210粉丝 390原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值