• 代价模型参数可以动态配置,可以适应不同的硬件
  • 区分考虑数据在内存和在磁盘中的代价
  • 代价精度提升为浮点型
  • jion计算时不仅要考虑condition,还要考虑condition上的filter,具体参见参数condition_fanout_filter

5.7 在代价类型上分为io,cpu和memory, 5.7的代价模型还在完善中,memory的代价虽然已经收集了,但还没有没有计算在最终的代价中。 5.7 在源码上对代价模型进行了大量重构,代价分为server层和engine层。server层主要是cpu的代价,而engine层主要是io的代价。 5.7 引入了两个系统表mysql.server_cost和mysql.engine_cost来分别配置这两个层的代价。 以下分析均基于mysql5.7.10

server_cost

  • row_evaluate_cost (default 0.2) 计算符合条件的行的代价,行数越多,此项代价越大
  • memory_temptable_create_cost (default 2.0) 内存临时表的创建代价
  • memory_temptable_row_cost (default 0.2) 内存临时表的行代价
  • key_compare_cost (default 0.1) 键比较的代价,例如排序
  • disk_temptable_create_cost (default 40.0) 内部myisam或innodb临时表的创建代价
  • disk_temptable_row_cost (default 1.0) 内部myisam或innodb临时表的行代价

由上可以看出创建临时表的代价是很高的,尤其是内部的myisam或innodb临时表。

  • io_block_read_cost (default 1.0) 从磁盘读数据的代价,对innodb来说,表示从磁盘读一个page的代价
  • memory_block_read_cost (default 1.0) 从内存读数据的代价,对innodb来说,表示从buffer pool读一个page的代价

    目前io_block_read_cost和memory_block_read_cost默认值均为1,实际生产中建议酌情调大memory_block_read_cost,特别是对普通硬盘的场景。

代价配置

cost参数可以通过修改mysql.server_cost和mysql.engine_cost来实现。初始这两个表中的记录cost_value项均为NULL, 代价值都取上两节介绍的初始值。 当修改cost_value为非NULL时,代价值按设定的值计算。修改方法如下:

另外,在主备环境下,修改cost参数时主备都要修改。因为mysql.server_cost和mysql.engine_cost的更新不会参与复制。

初始化数据

  1. let $loop=100;
  2. while($loop)
  3. {
  4. eval insert into t1(c1,c2,c3) values($loop, $loop+1, $loop+2);
  5. dec $loop;
  6. }
  7. set optimizer_trace = "enabled=on";

cost参数都取默认值,以下示例中会用到row_evaluate_cost(0.2),io_block_read_cost(1.0),io_block_read_cost(1.0),memory_block_read_cost(1.0)

以下语句选择覆盖索引c2

  1. explain select c1,c2 from t1 where c2 > 10;
  2. id select_type table partitions type possible_keys key key_len ref rows filtered Extra
  3. 1 SIMPLE t1 NULL range c2 c2 5 NULL 91 100.00 Using where; Using index

查看optimizer_trace, 可以看出全表扫描代价为23.1,通过c2上的索引扫描代价为19.309, 最后选择c2上的索引扫描。

  1. "rows_estimation": [
  2. {
  3. "table": "`t1`",
  4. "range_analysis": {
  5. "table_scan": {
  6. "rows": 100,
  7. "cost": 23.1
  8. },
  9. "potential_range_indexes": [
  10. {
  11. "index": "PRIMARY",
  12. "usable": false,
  13. "cause": "not_applicable"
  14. },
  15. {
  16. "index": "c2",
  17. "usable": true,
  18. "key_parts": [
  19. "c2"
  20. ]
  21. }
  22. ],
  23. "best_covering_index_scan": {
  24. "index": "c2",
  25. "cost": 21.109,
  26. "chosen": true
  27. },
  28. "setup_range_conditions": [
  29. ],
  30. "group_index_range": {
  31. "chosen": false,
  32. "cause": "not_group_by_or_distinct"
  33. },
  34. "analyzing_range_alternatives": {
  35. "range_scan_alternatives": [
  36. {
  37. "index": "c2",
  38. "ranges": [
  39. "10 < c2"
  40. ],
  41. "index_dives_for_eq_ranges": true,
  42. "rowid_ordered": false,
  43. "using_mrr": false,
  44. "index_only": true,
  45. "rows": 91,
  46. "cost": 19.309,
  47. "chosen": true
  48. }
  49. ],
  50. "usable": false,
  51. "cause": "too_few_roworder_scans"
  52. }
  53. },
  54. "chosen_range_access_summary": {
  55. "range_access_plan": {
  56. "type": "range_scan",
  57. "index": "c2",
  58. "rows": 91,
  59. "ranges": [
  60. "10 < c2"
  61. ]
  62. "rows_for_plan": 91,
  63. "cost_for_plan": 19.309,
  64. "chosen": true
  65. }
  66. }
  67. }
  68. ]
  69. },
  70. {
  71. "considered_execution_plans": [
  72. {
  73. "plan_prefix": [
  74. ],
  75. "table": "`t1`",
  76. "best_access_path": {
  77. "considered_access_paths": [
  78. {
  79. "rows_to_scan": 91,
  80. "access_type": "range",
  81. "range_details": {
  82. "used_index": "c2"
  83. },
  84. "resulting_rows": 91,
  85. "cost": 37.509,
  86. "chosen": true
  87. }
  88. ]
  89. },
  90. "condition_filtering_pct": 100,
  91. "rows_for_plan": 91,
  92. "cost_for_plan": 37.509,
  93. "chosen": true
  94. }
  95. ]

全表扫描的代价23.1

  1. test_quick_select
  2. double scan_time=
  3. cost_model->row_evaluate_cost(static_cast<double>(records)) + 1;
  4. Cost_estimate cost_est= head->file->table_scan_cost();
  5. cost_est.add_io(1.1);//这里加1.1应该是个调节值
  6. cost_est.add_cpu(scan_time);

其中io代价table_scan_cost会根据buffer pool大小和索引大小来估算page in memory和in disk的比例,分别算出代价。

page_read_cost计算读取单个page的代价

  1. buffer_block_read_cost(pages_in_mem) + io_block_read_cost(pages_on_disk);

io代价为1+1.1=2.1

cpu代价为row_evaluate_cost

  1. double row_evaluate_cost(double rows) const
  2. {
  3. DBUG_ASSERT(m_initialized);
  4. DBUG_ASSERT(rows >= 0.0);
  5. return rows * m_server_cost_constants->row_evaluate_cost(); // 100 * 0.2(row_evaluate_cost)=20;
  6. }

cpu代价为20+1=21;

最终代价为2.1+21=23.1

c2索引扫描代价19.309

同样也分为io和cpu代价

  1. multi_range_read_info_const
  2. *cost= index_scan_cost(keyno, static_cast<double>(n_ranges),
  3. static_cast<double>(total_rows));
  4. cost->add_cpu(cost_model->row_evaluate_cost(static_cast<double>(total_rows)) + 0.01);

io代价 1.0987925356750823*1=1.0987925356750823

  1. index_scan_cost:
  2. const double io_cost= index_only_read_time(index, rows) * //估算index占page个数 = 1.0987925356750823
  3. table->cost_model()->page_read_cost_index(index, 1.0); //根据buffer pool大小和索引大小来估算page in memory和in disk的比例,计算读一个page的代价。 = 1

cpu代价91*0.2+0.01=18.21

最终代价1.0987925356750823+18.21=19.309

示例2

  1. explain select * from t1 where c2 > 10;
  2. id select_type table partitions type possible_keys key key_len ref rows filtered Extra
  3. 1 SIMPLE t1 NULL ALL c2 NULL NULL NULL 100 91.00 Using where

查看optimizer_trace, 可以看出全表扫描代价为23.1,通过c2上的索引扫描代价为110.21, 最后选择全表扫描。

  1. "rows_estimation": [
  2. {
  3. "table": "`t1`",
  4. "range_analysis": {
  5. "table_scan": {
  6. "rows": 100,
  7. "cost": 23.1
  8. },
  9. "potential_range_indexes": [
  10. "index": "PRIMARY",
  11. "usable": false,
  12. "cause": "not_applicable"
  13. },
  14. {
  15. "usable": true,
  16. "key_parts": [
  17. "c2"
  18. ]
  19. }
  20. ],
  21. "setup_range_conditions": [
  22. ],
  23. "group_index_range": {
  24. "chosen": false,
  25. "cause": "not_group_by_or_distinct"
  26. },
  27. "analyzing_range_alternatives": {
  28. "range_scan_alternatives": [
  29. {
  30. "index": "c2",
  31. "ranges": [
  32. "10 < c2"
  33. ],
  34. "index_dives_for_eq_ranges": true,
  35. "rowid_ordered": false,
  36. "using_mrr": false,
  37. "index_only": false,
  38. "rows": 91,
  39. "cost": 110.21,
  40. "chosen": false,
  41. "cause": "cost"
  42. }
  43. ],
  44. "analyzing_roworder_intersect": {
  45. "usable": false,
  46. "cause": "too_few_roworder_scans"
  47. }
  48. }
  49. }
  50. }
  51. ]
  52. },
  53. {
  54. "considered_execution_plans": [
  55. {
  56. "plan_prefix": [
  57. ],
  58. "table": "`t1`",
  59. "best_access_path": {
  60. "considered_access_paths": [
  61. {
  62. "rows_to_scan": 100,
  63. "access_type": "scan",
  64. "resulting_rows": 91,
  65. "cost": 21,
  66. "chosen": true
  67. }
  68. ]
  69. },
  70. "condition_filtering_pct": 100,
  71. "rows_for_plan": 91,
  72. "cost_for_plan": 21,
  73. "chosen": true
  74. }
  75. ]
  76. },

全表扫描代价23.1

同上一节分析

c2索引扫描代价为110.21

上一节通过c2索引扫描代价为19.309,因为是覆盖索引不需要回表,所以代价较少。而此例是需要回表的。

  1. multi_range_read_info_const
  2. *cost= read_cost(keyno, static_cast<double>(n_ranges),
  3. static_cast<double>(total_rows));
  4. cost->add_cpu(cost_model->row_evaluate_cost(
  5. static_cast<double>(total_rows)) + 0.01);

io代价需回表

  1. read_cost: //92*1=92
  2. const double io_cost= read_time(index, static_cast<uint>(ranges)
  3. static_cast<ha_rows>(rows)) *
  4. table->cost_model()->page_read_cost(1.0);
  5. read_time: //91+1=92
  6. virtual double read_time(uint index, uint ranges, ha_rows rows)

这里回表时计算代价为每行代价为1,默认认为回表时每行都对于聚集索引的一个page.

io代价为92

cpu代价为91*0.2+0.01=18.21

最后代价为92+18.21=110.21

总结

5.7 代价模型优化还在持续改进中,相信后续的版本会越来越好。代价的参数的配置需谨慎,需要大量的测试和验证。