【GreatSQL优化器-10】find_best_ref

打印 上一主题 下一主题

主题 1027|帖子 1027|积分 3081

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
【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函数获得的结果。
  1. CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 INT,date1 DATETIME);
  2. 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');
  3. CREATE TABLE t2 (cc1 INT PRIMARY KEY, cc2 INT);
  4. INSERT INTO t2 VALUES (1,3),(2,1),(3,2),(4,3),(5,15);
  5. CREATE TABLE t3 (ccc1 INT, ccc2 varchar(100));
  6. INSERT INTO t3 VALUES (1,'aa1'),(2,'bb1'),(3,'cc1'),(4,'dd1'),(null,'ee');
  7. CREATE INDEX idx1 ON t1(c2);
  8. CREATE INDEX idx2 ON t1(c2,date1);
  9. CREATE INDEX idx2_1 ON t2(cc2);
  10. CREATE INDEX idx3_1 ON t3(ccc1);
  11. greatsql> SELECT * FROM t1 join t2 ON t1.c1=t2.cc1 and t1.c2<5;
  12.           {
  13.             "ref_optimizer_key_uses": [ 首先这里要有keyuse_array
  14.               {
  15.                 "table": "`t1`",
  16.                 "field": "c1",
  17.                 "equals": "`t2`.`cc1`",
  18.                 "null_rejecting": true
  19.               },
  20.               {
  21.                 "table": "`t2`",
  22.                 "field": "cc1",
  23.                 "equals": "`t1`.`c1`",
  24.                 "null_rejecting": true
  25.               }
  26.             ]
  27.           },
  28.             "considered_execution_plans": [
  29.               {
  30.                 "plan_prefix": [
  31.                 ],
  32.                 "table": "`t1`",
  33.                 "best_access_path": {
  34.                   "considered_access_paths": [
  35.                     {
  36.                       "access_type": "ref",
  37.                       "index": "PRIMARY",
  38.                       "usable": false,
  39.                       "chosen": false
  40.                     },
  41.                     {
  42.                       "rows_to_scan": 2,
  43.                       "filtering_effect": [
  44.                       ],
  45.                       "final_filtering_effect": 1,
  46.                       "access_type": "range",
  47.                       "range_details": {
  48.                         "used_index": "idx2"
  49.                       },
  50.                       "resulting_rows": 2,
  51.                       "cost": 0.660457,
  52.                       "chosen": true
  53.                     }
  54.                   ]
  55.                 },
  56.                 "condition_filtering_pct": 100,
  57.                 "rows_for_plan": 2,
  58.                 "cost_for_plan": 0.660457,
  59.                 "rest_of_plan": [
  60.                   {
  61.                     "plan_prefix": [
  62.                       "`t1`"
  63.                     ],
  64.                     "table": "`t2`",
  65.                     "best_access_path": {
  66.                       "considered_access_paths": [
  67.                         {
  68.                           "access_type": "eq_ref",
  69.                           "index": "PRIMARY",
  70.                           "rows": 1, 这里就是通过find_best_ref()获得的结果
  71.                           "cost": 0.7, 这里就是通过find_best_ref()获得的结果
  72.                           "chosen": true,
  73.                           "cause": "clustered_pk_chosen_by_heuristics"
  74.                         },
  75.                         {
  76.                           "access_type": "scan",
  77.                           "chosen": false,
  78.                           "cause": "covering_index_better_than_full_scan"
  79.                         }
  80.                       ]
  81.                     },
  82.                     "condition_filtering_pct": 100,
  83.                     "rows_for_plan": 2,
  84.                     "cost_for_plan": 1.36046,
  85.                     "chosen": true
  86.                   }
  87.                 ]
  88.               },
  89.               {
  90.                 "plan_prefix": [
  91.                 ],
  92.                 "table": "`t2`",
  93.                 "best_access_path": {
  94.                   "considered_access_paths": [
  95.                     {
  96.                       "access_type": "ref",
  97.                       "index": "PRIMARY",
  98.                       "usable": false,
  99.                       "chosen": false
  100.                     },
  101.                     {
  102.                       "rows_to_scan": 5,
  103.                       "filtering_effect": [
  104.                       ],
  105.                       "final_filtering_effect": 1,
  106.                       "access_type": "scan",
  107.                       "resulting_rows": 5,
  108.                       "cost": 0.75,
  109.                       "chosen": true
  110.                     }
  111.                   ]
  112.                 },
  113.                 "condition_filtering_pct": 100,
  114.                 "rows_for_plan": 5,
  115.                 "cost_for_plan": 0.75,
  116.                 "rest_of_plan": [
  117.                   {
  118.                     "plan_prefix": [
  119.                       "`t2`"
  120.                     ],
  121.                     "table": "`t1`",
  122.                     "best_access_path": {
  123.                       "considered_access_paths": [
  124.                         {
  125.                           "access_type": "eq_ref",
  126.                           "index": "PRIMARY",
  127.                           "rows": 1, 这里就是通过find_best_ref()获得的结果
  128.                           "cost": 5.5, 这里就是通过find_best_ref()获得的结果
  129.                           "chosen": true,
  130.                           "cause": "clustered_pk_chosen_by_heuristics"
  131.                         },
  132.                         {
  133.                           "rows_to_scan": 2,
  134.                           "filtering_effect": [
  135.                           ],
  136.                           "final_filtering_effect": 1,
  137.                           "access_type": "range",
  138.                           "range_details": {
  139.                             "used_index": "idx2"
  140.                           },
  141.                           "resulting_rows": 2,
  142.                           "cost": 3.30229,
  143.                           "chosen": true
  144.                         }
  145.                       ]
  146.                     },
  147.                     "condition_filtering_pct": 100,
  148.                     "rows_for_plan": 10,
  149.                     "cost_for_plan": 4.05229,
  150.                     "pruned_by_cost": true
  151.                   }
  152.                 ]
  153.               }
  154.             ]
复制代码
表:接纳的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:ptimize_keyuse()函数获得
表五:索引类型选择优先级
idx_type优先级阐明对应的access_typeCLUSTERED_PK最高primary key,优先级最高eq_refUNIQUE高唯一索引eq_refNOT_UNIQUE低非唯一索引refFULLTEXT最低fulltext索引,优先级最低ref三、实际例子阐明

接下来看一开始的例子来阐明上面的代码:
[code]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
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

南飓风

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表