MySQL优化器的成本模型

如果无法正常显示,请先停止浏览器的去广告插件。
分享至:
1. MySQL优化器的成本模型 周振兴@2016年7月
2. 目录 成本模型与关系型数据库 简单JOIN的执行 成本计算 MySQL常见access method的成本计算 MySQL成本计算中的统计信息 成本与执行计划选择 其他的细节
3. 成本模型与关系型数据库 图片来源: Query Optimization Yannis E. Ioannidis 1996
4. 示例 SELECT * FROM a,b WHERE a.num = 6 and a.bid = b.id and b.age > 17; Table: a CREATE TABLE `a` ( `id` int(11) NOT NULL DEFAULT '0', `num` int(11) DEFAULT NULL, `bid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_NUM` (`num`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 Table: b CREATE TABLE `b` ( `id` int(11) NOT NULL DEFAULT '0', `age` int(11) DEFAULT NULL, `nick` char(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_AGE` (`age`)) ENGINE=InnoDB DEFAULT CHARSET=latin1
5. 可能的执行计划 A ref IND_NUM ⋈ NLJ ⋈ NLJ ⋈ NLJ B eq_ref PK SELECT * FROM A,B WHERE A.num = 6 and A.bid = B.id and B.age > 17; B range IND_AGE A ALL B range IND_AGE A ref IND_NUM 一些事实与说明: 1. MySQL只支持Nested-Loop Join 2. 图示中,左边表总是Join中的outer table,右边总是inner table (也有说法,左边是驱动表driving table,右边是被驱动表)
6. 简要的执行过程 ⋈ NLJ for each tuple x in A with index IND_NUM for each tuple y in B with index PK (b.id = a.bid) if B.age > 17 return <x,y> endif A ref IND_NUM B eq_ref PK SELECT * FROM A,B WHERE A.num = 6 and A.bid = B.id and B.age > 17; 说明:tuple、row、record简单理解是指表中的一条记录
7. 理解NLJ(一) for each tuple x in A with index IND_NUM for each tuple y in B with index PK (b.id = a.bid) if B.age > 17 return <x,y> endif SELECT * FROM A,B WHERE A.num = 6 and A.bid = B.id and B.age > 17; 1. 访问索引IND_NUM获取A.num=6的rowid 2. 根据rowid,读取A表命中的记录 1. 读取主键索引中b.id = a.bid的页,取出对应tuple 1. 判断取出的tuple中B.age > 17
8. 理解NLJ(二) for each tuple x in A with index IND_NUM for each tuple y in B with index PK (b.id = a.bid) if B.age > 17 return <x,y> endif SELECT * FROM A,B WHERE A.num = 6 and A.bid = B.id and B.age > 17; 1. read index page (1) 2. Comparing*keys/records (131) 3. read data page (131) 1. 读取主键索引页,也就是读取了数据页 (read 1 data/index page) 1. evaluating query conditions
9. 目录 成本模型与关系型数据库 简单JOIN的执行 成本计算 MySQL常见access method的成本计算 MySQL成本计算中的统计信息 成本与执行计划选择 其他的细节
10. 成本分析 COST = COST of (IO + CPU) PAGE FETCHES RSI CALLS COST = PAGE FETCH + W * (RSI CALLS) 公式来源:Access Path Selection in a Relational Database Management System P. Griffiths Selinger... IBM 1979
11. MySQL成本分析 COST = COST of (IO + CPU) PAGE FETCHES RSI CALLS COST = PAGE FETCH + W * (RSI CALLS) Data Page Index Page compare key row evaluating MySQL成本细节 ……
12. Nested Loop JOIN的成本计算 Cost(NLJ) = C(A) + P_ROW(A) * C(B) 涉及的名词 解释 A outer table B inner table C(A) P_ROW(A) C(B) cost of outer table prefix row cost of every time evaluating inner table
13. 引入概念:权重W for each tuple x in A with index IND_NUM for each tuple y in B with index PK (b.id = a.bid) if B.age > 17 return <x,y> endif 1. read index page (1) 2. Comparing*keys/records (131) 3. read data page (131) 1. 读取主键索引页,也就是读取了数据页 (read 1 data/index page) 1. evaluating query conditions SELECT * FROM A,B WHERE A.num = 6 and A.bid = B.id and B.age > 17; (MySQL 5.7)
14. 成本计算 for each tuple x in A with index IND_NUM for each tuple y in B with index PK (b.id = a.bid) if B.age > 17 return <x,y> endif SELECT * FROM A,B WHERE A.num = 6 and A.bid = B.id and B.age > 17; 1. read index page (1) 2. Comparing*keys/records (131) 3. read data page (131) 1. 读取主键索引页,也就是读取了数据页 (read 1 data/index page) 1. evaluating query conditions Cost(NLJ) = C(A) + P_ROW(A) * C(B) Cost(NLJ) = 1 + 131 + 131*0.1 + 131*(1+1*0.2)
15. 成本模型与关系型数据库 简单JOIN的执行与成本 成本计算 MySQL常见access method的成本计算 MySQL成本计算中的统计信息 成本与执行计划选择 其他的细节
16. 回到前面的例子 A ref IND_NUM ⋈ NLJ ⋈ NLJ ⋈ NLJ B eq_ref PK SELECT * FROM A,B WHERE A.num = 6 and A.bid = B.id and B.age > 17; B range IND_AGE A ALL B range IND_AGE A ref IND_NUM 问题1:解释第二个执行计划的执行过程和成本计算? 问题2:一共有多少种执行计划? 问题3:能否列举其中的一个?
17. MySQL主要的access method • table scan where TRUE • index scan order by ind_a • range scan ind_a = 5 and ind_b > 10 • ref where ind_a = 97 / A.ind_a = B.col • ……
18. table scan的成本计算 SELECT * FROM a WHERE a.bid < 6 • 全表扫描,逐行读取所有记录 • 评估WHERE条件是否满足 Table: a CREATE TABLE `a` ( `id` int(11) NOT NULL DEFAULT '0', `num` int(11) DEFAULT NULL, `bid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_NUM` (`num`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 Cost = Page(Table A) + 0.2 * ROW(Table A) s->table->file->scan_time() s->table->file->stats.records;
19. index scan的成本计算 Table: a CREATE TABLE `a` ( `id` int(11) NOT NULL DEFAULT '0', `num` int(11) DEFAULT NULL, `bid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_NUM` (`num`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 SELECT * FROM a ORDER BY num • 全索引扫描,并返回对应的rowid • 根据rowid读取每一个记录 Cost = Page(INDEX IND_NUM) + ROW(Table A) handler::index_only_read_time stats.block_size key_length/ref_length records s->table->file->stats.records; 问题:如何计算索引页数
20. 上一页问题的MySQL实现 Cost = Page(INDEX IND_NUM) + ROW(Table A) handler::index_only_read_time stats.block_size key_length/ref_length records s->table->file->stats.records; 问题:如何计算索引页数
21. index scan的成本计算(覆盖扫描) SELECT num FROM a ORDER BY num Table: a CREATE TABLE `a` ( `id` int(11) NOT NULL DEFAULT '0', `num` int(11) DEFAULT NULL, `bid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_NUM` (`num`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 • 全索引扫描 Cost = Page(INDEX IND_NUM) handler::index_only_read_time
22. range scan的成本计算 SELECT * FROM a WHERE num > 6 and num <10 • 读取索引范围,并返回对应的rowid • 根据rowid读取每一个记录 Table: a CREATE TABLE `a` ( `id` int(11) NOT NULL DEFAULT '0', `num` int(11) DEFAULT NULL, `bid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_NUM` (`num`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 Cost = E_ROW(A) + E_ROW(A) * 0.1 records_in_range(keynr, * min_key, * max_key)
23. ref的成本计算(1) SELECT * FROM a WHERE num = 6 (注:有索引、有取值) • 读取索引范围,并返回对应的rowid • 根据rowid读取每一个记录 Table: a CREATE TABLE `a` ( `id` int(11) NOT NULL DEFAULT '0', `num` int(11) DEFAULT NULL, `bid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_NUM` (`num`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 Cost = E_ROW(A) + E_ROW(A) * 0.1 records_in_range(keynr, * min_key, * max_key)
24. ref的成本计算(2) SELECT * FROM b STRAIGHT_JOIN a WHERE a.num = b.age and b.age > 10 (注:有索引、无取值) • 读取索引范围,并返回对应的rowid • 根据rowid读取每一个记录 Table: a CREATE TABLE `a` ( `id` int(11) NOT NULL DEFAULT '0', `num` int(11) DEFAULT NULL, `bid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_NUM` (`num`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 Cost(A) = E_ROW(A) + E_ROW(A) * 0.1 (records= keyinfo->rec_per_key[actual_key_parts(keyinfo)-1])
25. 部分MySQL统计信息 更新策略: s->table->file->scan_time() 全表扫描页数 s->table->file->stats.records 表总记录数 stats.block_size 块大小 key_length/ref_length 索引信息 records_in_range 范围中的记录数 keyinfo->rec_per_key 单个索引值引用的rowid数量 … … • ANALYZE TABLE • SHOW TABLE STATUS • 第一次访问表 • 访问表: • INFORMATION_SCHEMA.TABLES • INFORMATION_SCHEMA.STATISTICS • 在变更记录数超过1/16的时候 更新策略的控制: • innodb_stats_on_metadata
26. 小节 • 至此,我们知道了: • 各种单表各种access method的成本计算方法 • 两个表做NJL的成本计算方法 • 那么,进一步,我们可以计算: • 多表NLJ的成本计算:这个是一个递归计算 • 我们可以比较不同的执行计划的成本差异
27. 目录 成本模型与关系型数据库 简单JOIN的执行 成本计算 MySQL常见access method的成本计算 MySQL成本计算中的统计信息 成本与执行计划选择 其他的细节
28. 三个表JOIN的场景 N个表JOIN的场景
29. 约定:简化的写法 ⋈ NLJ A ⋈ NLJ B 简化的写法 A (ref IND_NUM) ref IND_NUM eq_ref PK B (eq_ref PK)
30. 示例 SELECT * FROM WHERE A.num and B.id and C.age and A.cid and B.aid A,B,C = = > = = 6 100 17 C.id A.id
31. 可能的执行计划 ⋈ NLJ ⋈ NLJ ⋈ NLJ C (range IND_AGE) A B (ref IND_NUM) (eq_ref PK) ⋈ NLJ B (eq_ref PK) A (table scan) C (range IND_AGE) 一共有多少个这样的执行计划?
32. N个表的执行计划 ⋈ NLJ ⋈ NLJ 如何找到这个问题的最优解? …… 1. 穷举 复杂度:O(N!) ⋈ NLJ 2. 贪婪搜索 复杂度 ⋈ NLJ 3. 启发式(heuristics)的搜索 注:简化了如下场景 • 只考虑NLJ,不考虑sort-merge和hash join • 没有加入关于interesting order的情况 A (ref) C (range) B (eq_ref) X (all)
33. N个表的执行计划-贪婪搜索 ⋈ NLJ ⋈ NLJ 如何找到这个问题的最优解? …… 1. 穷举 复杂度:O(N!) ⋈ NLJ 2. 贪婪搜索 复杂度 ⋈ NLJ 3. 启发式(heuristics)的『裁枝』 注:简化了如下场景 • 只考虑NLJ,不考虑sort-merge和hash join • 没有加入关于interesting order的情况 A (ref) C (range) B (eq_ref) X (all)
34. N个表的执行计划-贪婪搜索 ⋈ NLJ ⋈ NLJ 如何贪婪:把局部最优解当做全局最优解。 这里假设『局部最优解』的计算深度是depth, 那么复杂度为: 𝑁 ∗ 𝑁 &'()* 𝑂( ) depth 问题:如果depth=1,蜕化后的情况是怎样的? ⋈ NLJ A (ref) …… ⋈ NLJ C (range) B (eq_ref) X (all)
35. N个表的执行计划-贪婪搜索 ⋈ NLJ ⋈ NLJ 如何找到这个问题的最优解? …… 1. 穷举 复杂度:O(N!) ⋈ NLJ 2. 贪婪搜索 复杂度 ⋈ NLJ 3. 启发式(heuristics)的『裁枝』 skip certain plans based on estimates of the number of rows accessed for each table 注:简化了如下场景 • 只考虑NLJ,不考虑sort-merge和hash join • 没有加入关于interesting order的情况 A (ref) C (range) B (eq_ref) X (all)
36. 理论很复杂,实际很简单 ⋈ NLJ • 一般的,N < depth = 64,且prune_level = 1 ⋈ NLJ • 基本上都是穷举 …… • 贪婪搜索过程中,要选择下一个被JOIN的 表的时候,只看这个表返回的行数 ⋈ NLJ A (ref) ⋈ NLJ C (range) B (eq_ref) X (all)
37. 看起来简单,但细节非常多 • 这里只考虑的NLJ,忽略sort-merge和hash join • 没有考虑NLJ的一些优化 • Block Nested Loops Join (MySQL) • 为了简化,忽略了『interesting order』(order by/group by等) • 没有讨论为什么总是left-deep tree • 没有考虑nested query(subquery)的成本计算或者semi-join转换 • 为了简化,没有考虑多个谓词,对prefix row的影响(filter) • 没有考虑condition_fanout_filter (MySQL5.7) • 没有讨论GROUP BY/ORDER BY/DISTINCT等优化
38. 参考和扩展阅读 • Paper • Query Optimization Yannis E. Ioannidis 文章链接 • Access Path Selection in a Relational Database Management System P. Griffiths Selinger... IBM • 一些slide: • MySQL queryoptimizer internalsand upcomingfeatures in v. 5.2 • Implementing Joins Implementation of Database Systems • MySQL Cost Model • 其他 • MySQL Internals Manual • MySQL source code • MySQL查询优化浅析 何登成
39. 示例 SELECT * FROM a,b WHERE a.num = 6 and a.bid = b.id and b.age > 17; Table: a CREATE TABLE `a` ( `id` int(11) NOT NULL DEFAULT '0', `num` int(11) DEFAULT NULL, `bid` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_NUM` (`num`)) ENGINE=InnoDB DEFAULT CHARSET=latin1 Table: b CREATE TABLE `b` ( `id` int(11) NOT NULL DEFAULT '0', `age` int(11) DEFAULT NULL, `nick` char(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `IND_AGE` (`age`)) ENGINE=InnoDB DEFAULT CHARSET=latin1
40. 数据 for i in `seq 0 5000`;do mysql -v -uroot test -e “insert into a values (rand()*10000,rand()*10000,rand()*10000)”; done select substring(md5(concat("adfasdfasfasdf",rand()*1000000)),1,rand()*10); for i in `seq 0 500`;do mysql -v -uroot test -e "insert into b values (rand()*100000,rand()*100000,substring(md5(concat('adfasdfasfasdf',rand()*100 0000)),1,rand()*10))"; done
41. 附录2:Blocked Nested-Loop Join ⋈ NLJ for each tuple x in A with index IND_NUM store used columns from A in join buffer A B for each tuple y in B with index PK (b.id = a.bid) for each items z in join buffer if B.age > 17 return <z,y> endif inner table被扫描的次数: (S * C)/join_buffer_size + 1 S Size of (x interesting column) C Row return from A ref IND_NUM eq_ref PK SELECT * FROM A,B WHERE A.num = 6 and A.bid = B.id and B.age > 17; 说明:tuple、row、record简单理解是指表中的一条记录

首页 - Wiki
Copyright © 2011-2024 iteam. Current version is 2.132.0. UTC+08:00, 2024-09-22 04:28
浙ICP备14020137号-1 $访客地图$