南飓风 发表于 2025-1-10 09:34:20

【GreatSQL优化器-10】find_best_ref

【GreatSQL优化器-10】find_best_ref

一、find_best_ref介绍

GreatSQL的优化器对于join的表需要根据行数和cost来确定末了哪张表先实行哪张表后实行,这内里就涉及到预估满足条件的表数据,在keyuse_array数组有值的情况下,会用find_best_ref函数来通过索引进行cost和rows的估计,并且会找出最优的索引。这样就可能不会继承用后面的calculate_scan_cost()进行全表扫描盘算cost,可以节省查询时间。
这个功能是对之前【优化器05-条件过滤】的补充功能,二者有可能一起用,也有可能只选择一种盘算,要看具体条件。
下面用一个简单的例子来阐明find_best_ref函数获得的结果。
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'),(7,null,'2020-03-25 16:44:00.123456'),(8,10,'2020-10-25 16:44:00.123456'),(11,16,'2023-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);

greatsql> SELECT * FROM t1 join t2 ON t1.c1=t2.cc1 and t1.c2<5;
          {
            "ref_optimizer_key_uses": [ 首先这里要有keyuse_array
            {
                "table": "`t1`",
                "field": "c1",
                "equals": "`t2`.`cc1`",
                "null_rejecting": true
            },
            {
                "table": "`t2`",
                "field": "cc1",
                "equals": "`t1`.`c1`",
                "null_rejecting": true
            }
            ]
          },
            "considered_execution_plans": [
            {
                "plan_prefix": [
                ],
                "table": "`t1`",
                "best_access_path": {
                  "considered_access_paths": [
                  {
                      "access_type": "ref",
                      "index": "PRIMARY",
                      "usable": false,
                      "chosen": false
                  },
                  {
                      "rows_to_scan": 2,
                      "filtering_effect": [
                      ],
                      "final_filtering_effect": 1,
                      "access_type": "range",
                      "range_details": {
                        "used_index": "idx2"
                      },
                      "resulting_rows": 2,
                      "cost": 0.660457,
                      "chosen": true
                  }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 2,
                "cost_for_plan": 0.660457,
                "rest_of_plan": [
                  {
                  "plan_prefix": [
                      "`t1`"
                  ],
                  "table": "`t2`",
                  "best_access_path": {
                      "considered_access_paths": [
                        {
                        "access_type": "eq_ref",
                        "index": "PRIMARY",
                        "rows": 1, 这里就是通过find_best_ref()获得的结果
                        "cost": 0.7, 这里就是通过find_best_ref()获得的结果
                        "chosen": true,
                        "cause": "clustered_pk_chosen_by_heuristics"
                        },
                        {
                        "access_type": "scan",
                        "chosen": false,
                        "cause": "covering_index_better_than_full_scan"
                        }
                      ]
                  },
                  "condition_filtering_pct": 100,
                  "rows_for_plan": 2,
                  "cost_for_plan": 1.36046,
                  "chosen": true
                  }
                ]
            },
            {
                "plan_prefix": [
                ],
                "table": "`t2`",
                "best_access_path": {
                  "considered_access_paths": [
                  {
                      "access_type": "ref",
                      "index": "PRIMARY",
                      "usable": false,
                      "chosen": false
                  },
                  {
                      "rows_to_scan": 5,
                      "filtering_effect": [
                      ],
                      "final_filtering_effect": 1,
                      "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": [
                      "`t2`"
                  ],
                  "table": "`t1`",
                  "best_access_path": {
                      "considered_access_paths": [
                        {
                        "access_type": "eq_ref",
                        "index": "PRIMARY",
                        "rows": 1, 这里就是通过find_best_ref()获得的结果
                        "cost": 5.5, 这里就是通过find_best_ref()获得的结果
                        "chosen": true,
                        "cause": "clustered_pk_chosen_by_heuristics"
                        },
                        {
                        "rows_to_scan": 2,
                        "filtering_effect": [
                        ],
                        "final_filtering_effect": 1,
                        "access_type": "range",
                        "range_details": {
                            "used_index": "idx2"
                        },
                        "resulting_rows": 2,
                        "cost": 3.30229,
                        "chosen": true
                        }
                      ]
                  },
                  "condition_filtering_pct": 100,
                  "rows_for_plan": 10,
                  "cost_for_plan": 4.05229,
                  "pruned_by_cost": true
                  }
                ]
            }
            ]表:接纳的rows和cost结果
接纳的rows和cost结果场景(以下任一个条件满足)find_best_ref()的结果find_best_ref()方式找到的行数和cost都小于之前estimate_rowcount盘算的值生成了mm tree并且找到最优索引,并且range_scan()->type结果为ROWID_INTERSECTION没有生成mm tree但是找到最优索引force index方式calculate_scan_cost()的结果以上都不满足表:find_best_ref函数获得的结果
函数结果阐明值cur_fanout总共需要读取多少行盘算方式见表一cur_read_cost读的开销盘算方式见表二best_refcost最低的索引索引优先级见表五表一:cur_fanout盘算方式
场景值阐明唯一索引1索引覆盖全部涉及列并且条件是列等于常量table->quick_keys有值table->quick_rowstable->quick_keys无值tab->records() / distinct_keys_estdistinct_keys_est表示的是一张表对应一个索引值的行数有几行,盘算方式见表三索引覆盖全部涉及列并且条件不是列等于常量keyinfo->has_records_per_key()keyinfo->records_per_key盘算方式见表四!keyinfo->has_records_per_key()((double)tab->records() / (double)distinct_keys_est * (1.0 + ((double)(table->s->max_key_length - keyinfo->key_length) / (double)table->s->max_key_length))); if (cur_fanout < 2.0) cur_fanout = 2.0索引不能覆盖全部涉及列并且条件是列等于常量table->quick_rows索引不能覆盖全部涉及列并且条件不是列等于常量当前用的索引keyinfo->has_records_per_key()keyinfo->records_per_keyrecords_per_key表示的是一张表对应一个索引值的行数有几行,盘算方式见表四当前用的索引没有keyinfo->has_records_per_key()(x * (b-a) + a*c-b)/(c-1)b = records matched by whole key a = records matched by first key part c = number of key parts in key x = used key parts (1 page_read_cost(1.0)索引覆盖全部涉及列prefix_rowcount * find_cost_for_ref(thd, table, key, cur_fanout, tab->worst_seeks)索引不能覆盖全部涉及列prefix_rowcount * find_cost_for_ref(thd, table, key, cur_fanout, tab->worst_seeks)表三:distinct_keys_est盘算方式
场景值初始值tab->records() / 10distinct_keys_est > keyuse->ref_table_rowskeyuse->ref_table_rowsdistinct_keys_est < 1010tab->records() < distinct_keys_esttab->records()注:10是MATCHING_ROWS_IN_OTHER_TABLE
表四:keyuse->ref_table_rows盘算方式
keyuse->used_tables场景值PSEUDO_TABLE_BITS只有一张表max(tmp_table->file->stats.records, 100) 这里100是固定值OUTER_REF_TABLE_BIT有子查询的话只有一行满足1注:通过JOIN::optimize_keyuse()函数获得
表五:索引类型选择优先级
idx_type优先级阐明对应的access_typeCLUSTERED_PK最高primary key,优先级最高eq_refUNIQUE高唯一索引eq_refNOT_UNIQUE低非唯一索引refFULLTEXT最低fulltext索引,优先级最低ref三、实际例子阐明

接下来看一开始的例子来阐明上面的代码:
greatsql> SELECT * FROM t1 JOIN t2 ON t1.c1=t2.cc1 AND t1.c2 SELECT * FROM t1 JOIN t2 JOIN t3 ON t1.c1=t2.cc1 AND t3.ccc1=t2.cc1 AND t1.c2
页: [1]
查看完整版本: 【GreatSQL优化器-10】find_best_ref