当前位置:首页 > 数据库 > 正文内容

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

邻居的猫1个月前 (12-09)数据库2008

【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_rowcount join左表的行数,意味着多少行会被join到右表 关于第一张表来说prefix_rowcount=1 第一张表:prefix_rowcount = rows_fetched * filter_effect 非第一张表:prefix_rowcount = 上一张表的prefix_rowcount * rows_fetched * filter_effect
prefix_cost join左表的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() 核算办法

场景 阐明
满意条件的表的行数为0 filter = 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.05 filter = 0.05 / fanout fanout是满意条件的表的行数

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

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

Item的系数 解说 系数 阐明
COND_FILTER_ALLPASS Always true 1.0f 代表悉数数据都契合,悉数都要扫描
COND_FILTER_EQUALITY col1 = col2 0.1f 代表预估10%数据契合条件
COND_FILTER_INEQUALITY col1 > col2 0.3333f 代表预估1/3数据契合条件
COND_FILTER_BETWEEN col1 BETWEEN a AND b 0.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是总行数。
    # 第2次:核算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) |
                                                                                            # 看到这儿挑选了不做条件过滤,

扫描二维码推送至手机访问。

版权声明:本文由51Blog发布,如需转载请注明出处。

本文链接:https://www.51blog.vip/?id=523

标签: GreatSQL
分享给朋友:

“【GreatSQL优化器-05】条件过滤condition_fanout_filter” 的相关文章

SQL Server存储进程的优缺点

SQL Server存储进程的优缺点

SQL Server存储进程(Stored Procedure)是一组为了完结特定功用的SQL句子调集,经编译后存储在数据库中。存储进程能够接纳参数、履行相关SQL后回来成果。用户经过指定存储进程的姓名并给出参数(带参存储进程)来履行它。以下是运用SQL Server存储进程的一些利害,仅供参考:...

Redis中的分布式锁(稳扎稳打)

Redis中的分布式锁(稳扎稳打)

分布式锁 概述 分布式锁指的是,一切服务中的一切线程都去获取同一把锁,但只要一个线程能够成功的取得锁,其他没有取得锁的线程有必要悉数等候,直到持有锁的线程开释锁。 分布式锁是能够跨过多个实例,多个进程的锁 分布式锁具有的条件: 互斥性:恣意时刻,只能有一个客户端持有锁 锁超时开释:持有锁超时,能够...

oracle删除所有表,Oracle数据库中删除所有表的全面指南

Oracle数据库中删除所有表的全面指南在Oracle数据库管理中,有时可能需要删除所有的表,这可能是因为数据库重构、迁移到新版本或者清理不再需要的测试数据。本文将详细介绍如何在Oracle数据库中删除所有表,并提供一些重要的注意事项。准备工作在执行删除所有表的操作之前,以下准备工作是必不可少的:...

mysql和redis的区别,深入解析两种数据库技术的区别

mysql和redis的区别,深入解析两种数据库技术的区别

MySQL和Redis是两种常用的数据库系统,但它们的设计理念和用途有很大的不同。以下是它们之间的一些主要区别:1. 数据类型: MySQL:关系型数据库,使用SQL语言进行数据操作,支持复杂的数据结构和关系,如表、视图、存储过程等。 Redis:键值存储系统,支持多种数据类型,如字符串...

大数据 统计,大数据时代的来临与统计学的变革

大数据 统计,大数据时代的来临与统计学的变革

大数据统计是大数据分析中的一个重要组成部分,它涉及到对大量数据集进行收集、处理、分析和解释,以提取有价值的信息和洞察。以下是大数据统计的一些关键方面:1. 数据收集:大数据统计的第一步是收集大量数据。这些数据可以来自各种来源,包括社交媒体、网站、传感器、交易记录等。2. 数据预处理:在分析之前,需要...

向量数据库股票代码是什么,揭秘股票市场中的数据宝藏

向量数据库股票代码是什么,揭秘股票市场中的数据宝藏

向量数据库在股票市场中的应用可以帮助散户投资者更好地筛选和分析数据。以下是几个相关的龙头股上市公司及其股票代码:1. 中科信息(300678) 公司专注于向量数据库研发和应用,拥有自主知识产权的图数据库产品StellarDB,以及向量搜索引擎和向量计算引擎等配套产品。2. 云创数据(30005...