package com.taobao.tddl.optimizer.costbased.chooser; import java.util.Collections; import java.util.List; import java.util.Map; import com.taobao.tddl.common.model.ExtraCmd; import com.taobao.tddl.common.utils.GeneralUtil; import com.taobao.tddl.optimizer.config.table.IndexMeta; import com.taobao.tddl.optimizer.core.ast.ASTNode; import com.taobao.tddl.optimizer.core.ast.QueryTreeNode; import com.taobao.tddl.optimizer.core.ast.QueryTreeNode.FilterType; import com.taobao.tddl.optimizer.core.ast.query.JoinNode; import com.taobao.tddl.optimizer.core.ast.query.MergeNode; import com.taobao.tddl.optimizer.core.ast.query.QueryNode; import com.taobao.tddl.optimizer.core.ast.query.TableNode; import com.taobao.tddl.optimizer.core.expression.IBooleanFilter; import com.taobao.tddl.optimizer.core.expression.IFilter; import com.taobao.tddl.optimizer.core.expression.ILogicalFilter; import com.taobao.tddl.optimizer.core.expression.IOrderBy; import com.taobao.tddl.optimizer.core.expression.ISelectable; import com.taobao.tddl.optimizer.core.plan.query.IJoin.JoinStrategy; import com.taobao.tddl.optimizer.costbased.FilterSpliter; import com.taobao.tddl.optimizer.costbased.esitimater.Cost; import com.taobao.tddl.optimizer.costbased.esitimater.CostEsitimaterFactory; import com.taobao.tddl.optimizer.costbased.pusher.OrderByPusher; import com.taobao.tddl.optimizer.exceptions.QueryException; import com.taobao.tddl.optimizer.utils.FilterUtils; /** * 优化一下join * * <pre> * 优化策略: * 1. 选择合适的索引 * 2. 基于选择的索引,拆分where条件为key/indexValue/Result filter * 3. 针对不至此or条件的引擎,拆分OR为两个TableNode,进行merge查询. (需要考虑重复数据去重) * 4. 选择join策略 * 如果内表是一个TableNode,或者是一个紧接着TableNode的QueryNode * 先只考虑约束条件,而不考虑Join条件分以下两种大类情况: * 1.内表没有选定索引(约束条件里没有用到索引的列) * 1.1内表进行Join的列不存在索引 * 策略:NestLoop,内表使用全表扫描 * 1.2内表进行Join的列存在索引 * 策略:IndexNestLoop,在Join列里面选择索引,原本的全表扫描会分裂成一个Join * * 2.内表已经选定了索引(KeyFilter存在) * 2.1内表进行Join的列不存在索引 * 策略:NestLoop,内表使用原来的索引 * 2.2内表进行Join的列存在索引 * 这种情况最为复杂,有两种方法 * a. 放弃原来根据约束条件选择的索引,而使用Join列中得索引(如果有的话),将约束条件全部作为ValueFilter,这样可以使用IndexNestLoop * b. 采用根据约束条件选择的索引,而不管Join列,这样只能使用NestLoop * 如果内表经约束后的大小比较小,则可以使用方案二,反之,则应使用方案一,不过此开销目前很难估算。 * 或者枚举所有可能的情况,貌似也比较麻烦,暂时只采用方案二,实现简单一些。 * 5. 下推join/merge的order by条件 * * 如果设置了join节点顺序选择,会对可join的节点进行全排列,选择最合适的join,比如左表的数据最小 * </pre> */ public class JoinChooser { /** * @param qtn * @param extraCmd * @return */ public static QueryTreeNode optimize(QueryTreeNode qtn, Map<String, Object> extraCmd) { optimizeSubQuery(qtn, extraCmd);// 先遍历完成对QueryNode的子优化,因为这个优化不会在optimizeJoin处理 qtn = optimizeJoin(qtn, extraCmd); qtn.build(); return qtn; } /** * <pre> * 由于优化过程中需要将QueryTree转换为执行计划树,而外部查询转换为执行计划树是依赖于子查询的 * 所以需要先对子查询进行优化,再对外层查询进行优化,回溯完成 * </pre> */ private static void optimizeSubQuery(QueryTreeNode qtn, Map<String, Object> extraCmd) throws QueryException { if (qtn instanceof QueryNode) { QueryNode qn = (QueryNode) qtn; if (qn.getChild() != null) { optimizeSubQuery(qn.getChild(), extraCmd); qn.setChild(JoinChooser.optimize(qn.getChild(), extraCmd)); } } else if (qtn instanceof JoinNode) { JoinNode jn = (JoinNode) qtn; optimizeSubQuery(jn.getLeftNode(), extraCmd); optimizeSubQuery(jn.getRightNode(), extraCmd); } else {// table/merge if ((!qtn.getChildren().isEmpty()) && qtn.getChildren().get(0) != null) { optimizeSubQuery((QueryTreeNode) qtn.getChildren().get(0), extraCmd); } } } /** * 优化一棵查询树,以QueryNode为叶子终止,子节点单独进行优化 */ private static QueryTreeNode optimizeJoin(QueryTreeNode qtn, Map<String, Object> extraCmd) { // 暂时跳过可能存在的聚合函数 if (!(qtn instanceof TableNode || qtn instanceof JoinNode)) { qtn.getChildren().set(0, optimize((QueryTreeNode) qtn.getChildren().get(0), extraCmd)); return qtn; } boolean needReChooserJoinOrder = needReChooseJoinOrder(qtn); JoinPermutationGenerator jpg = null; if (needReChooserJoinOrder || isOptimizeJoinOrder(extraCmd)) { jpg = new JoinPermutationGenerator(qtn); qtn = jpg.getNext(); } long minIo = Long.MAX_VALUE; QueryTreeNode minCostQueryTree = null; // 枚举每一种join的顺序,并计算开销,每次保留当前开销最小的Join次序 while (qtn != null) { qtn = chooseStrategyAndIndexAndSplitQuery(qtn, extraCmd); qtn = qtn.convertToJoinIfNeed(); qtn = OrderByPusher.optimize(qtn); if (isOptimizeJoinOrder(extraCmd)) { // 计算开销 Cost cost = CostEsitimaterFactory.estimate(qtn); if (cost.getScanCount() < minIo) { minIo = cost.getScanCount(); minCostQueryTree = qtn; } qtn = jpg.getNext(); } else if (needReChooserJoinOrder) { qtn = jpg.getNext(); needReChooserJoinOrder = false; } else { // 不需要进行join选择,直接退出 minCostQueryTree = qtn; break; } } minCostQueryTree.build(); return minCostQueryTree; } /** * 判断是否需要调整join顺序 * * <pre> * 比如mysql: select xx from a,b,c where a.id = c.id and b.name = c.name * 这时的结构树为 (a join b) join c , a join b上不存在join条件,需要调整join顺序为 (a join c) join b 或者 (b join c) join a * </pre> */ private static boolean needReChooseJoinOrder(QueryTreeNode qtn) { if (qtn instanceof JoinNode) { if (((JoinNode) qtn).getJoinFilter() == null || ((JoinNode) qtn).getJoinFilter().isEmpty()) { return true; } } for (ASTNode node : qtn.getChildren()) { if (!(node instanceof QueryTreeNode)) { return false; } if (needReChooseJoinOrder((QueryTreeNode) node)) { return true; } } return false; } /** * 遍历每个节点 分解Query 为Query选择索引与Join策略 只遍历一棵子查询树 */ private static QueryTreeNode chooseStrategyAndIndexAndSplitQuery(QueryTreeNode node, Map<String, Object> extraCmd) throws QueryException { if (node instanceof JoinNode) { for (int i = 0; i < node.getChildren().size(); i++) { QueryTreeNode child = (QueryTreeNode) node.getChildren().get(i); child = chooseStrategyAndIndexAndSplitQuery(child, extraCmd); node.getChildren().set(i, child); } } if (node instanceof TableNode) { // Query是对实体表进行查询 List<QueryTreeNode> ss = FilterSpliter.splitByDNF((TableNode) node, extraCmd); // 如果子查询中得某一个没有keyFilter,也即需要做全表扫描 // 那么其他的子查询也没必要用索引了,都使用全表扫描即可 // 直接把原来的约束条件作为valuefilter,全表扫描就行。 boolean isExistAQueryNeedTableScan = false; for (QueryTreeNode s : ss) { if (s instanceof TableNode && ((TableNode) s).isFullTableScan()) { isExistAQueryNeedTableScan = true; } } if (isExistAQueryNeedTableScan) { ((TableNode) node).setIndexQueryValueFilter(null); ((TableNode) node).setKeyFilter(null); ((TableNode) node).setResultFilter(null); ss.clear(); ss = null; ((TableNode) node).setFullTableScan(true); } if (ss != null && ss.size() > 1) { MergeNode merge = new MergeNode(); merge.alias(ss.get(0).getAlias()); // limit操作在merge完成 for (QueryTreeNode s : ss) { merge.merge(s); } merge.setUnion(true); merge.build(); return merge; } else if (ss != null && ss.size() == 1) { return ss.get(0); } else { // 出现了ss为null,即代表需要全表扫描 // 查找可以包含所有选择列的索引 IndexMeta indexWithAllColumnsSelected = IndexChooser.findBestIndexByAllColumnsSelected(((TableNode) node).getTableMeta(), ((TableNode) node).getColumnsRefered(), extraCmd); if (indexWithAllColumnsSelected != null) { // 如果存在索引,则可以使用index value filter ((TableNode) node).useIndex(indexWithAllColumnsSelected); ((TableNode) node).setIndexQueryValueFilter(node.getWhereFilter()); } else { // 没有索引,则可以使用result filter ((TableNode) node).setResultFilter(node.getWhereFilter()); } return node; } } else if (node instanceof JoinNode) { // 如果右表是subquery,则也不能用indexNestedLoop if (((JoinNode) node).getRightNode().isSubQuery()) { ((JoinNode) node).setJoinStrategy(JoinStrategy.NEST_LOOP_JOIN); // 将未能下推的条件加到result filter中 ((JoinNode) node).setResultFilter(FilterUtils.and(node.getResultFilter(), node.getWhereFilter())); return node; } /** * <pre> * 如果内表是一个TableNode,或者是一个紧接着TableNode的QueryNode * 考虑orderby条件和join类型 * 1. 如果是outter join * a. 存在order by * i. join列是一个orderBy列的子集,并且是一个前序匹配,选择SortMergeJoin * ii. 不满足条件i,需要做本地排序,按照join列, 选择SortMergeJoin * b. 不存在order by,存在group by * i. join列是一个orderBy列的子集,调整group by的顺序,选择SortMergeJoin * ii. 不满足条件i,需要做本地排序,按照join列, 选择SortMergeJoin * c. 不满足a和b时,选择SortMergeJoin,下推join列做为排序条件 * 2. left outter/right outter join * a. 对应的outter表上存在order by字段时,join列是一个orderBy列的子集,并且是一个前序匹配,选择SortMergeJoin * b. 对应的outter表上存在group by字段时,join列是一个orderBy列的子集,调整groupBy顺序,选择SortMergeJoin * * 其余case考虑约束条件,而不考虑Join条件分以下两种大类情况: * 1.内表没有选定索引(约束条件里没有用到索引的列) * 1.1内表进行Join的列不存在索引 * 策略:NestLoop,内表使用全表扫描 * 1.2内表进行Join的列存在索引 * 策略:IndexNestLoop,在Join列里面选择索引,原本的全表扫描会分裂成一个Join * * 2.内表已经选定了索引(KeyFilter存在) * 2.1内表进行Join的列不存在索引 * 策略:NestLoop,内表使用原来的索引 * 2.2内表进行Join的列存在索引 * 这种情况最为复杂,有三种方法 * a. 如果join列和索引选择相同,这样可以使用IndexNestLoop * b. 放弃原来根据约束条件选择的索引,而使用Join列中得索引(如果有的话),将约束条件全部作为ValueFilter,这样可以使用IndexNestLoop * c. 采用根据约束条件选择的索引,而不管Join列,这样只能使用NestLoop * 如果内表经约束后的大小比较小,则可以使用方案二,反之,则应使用方案一,不过此开销目前很难估算。 * 或者枚举所有可能的情况,貌似也比较麻烦,暂时只采用方案二,实现简单一些。 * </pre> */ if (((JoinNode) node).isOuterJoin()) { // 几种分支都是选择sort merge join // join列的处理会在JoinNode.getImplicitOrderBys()中进行 ((JoinNode) node).setJoinStrategy(JoinStrategy.SORT_MERGE_JOIN); return node; } else if (((JoinNode) node).isLeftOuterJoin() || ((JoinNode) node).isRightOuterJoin()) { if (canChooseSortMerge((JoinNode) node)) { ((JoinNode) node).setJoinStrategy(JoinStrategy.SORT_MERGE_JOIN); return node; } } QueryTreeNode innerNode = ((JoinNode) node).getRightNode(); if (innerNode instanceof TableNode) { if (!((TableNode) innerNode).containsKeyFilter()) { String tablename = ((TableNode) innerNode).getTableName(); if (innerNode.getAlias() != null) { tablename = innerNode.getAlias(); } // 找到对应join字段的索引 IndexMeta index = IndexChooser.findBestIndex((((TableNode) innerNode).getTableMeta()), ((JoinNode) node).getRightKeys(), Collections.<IFilter> emptyList(), tablename, extraCmd); ((TableNode) innerNode).useIndex(index); buildTableFilter(((TableNode) innerNode)); if (index == null) {// case 1.1 ((JoinNode) node).setJoinStrategy(JoinStrategy.NEST_LOOP_JOIN); } else {// case 1.2 for (IBooleanFilter filter : ((JoinNode) node).getJoinFilter()) { ISelectable rightColumn = (ISelectable) filter.getValue(); if (index.getKeyColumn(rightColumn.getColumnName()) == null) { node.addResultFilter(filter); // 非索引的列 } } // 删除join条件中的非索引的列,将其做为where条件 if (node.getResultFilter() != null) { if (node.getResultFilter() instanceof IBooleanFilter) { ((JoinNode) node).getJoinFilter().remove(node.getResultFilter()); } else { ((JoinNode) node).getJoinFilter() .removeAll(((ILogicalFilter) ((JoinNode) node).getResultFilter()).getSubFilter()); } node.build(); } ((JoinNode) node).setJoinStrategy(JoinStrategy.INDEX_NEST_LOOP); } } else { IndexMeta index = ((TableNode) innerNode).getIndexUsed();// 一定存在 boolean isCover = true; for (IBooleanFilter filter : ((JoinNode) node).getJoinFilter()) { ISelectable rightColumn = (ISelectable) filter.getValue(); if (index.getKeyColumn(rightColumn.getColumnName()) == null) { isCover = false; // join中出现index没有的列 } } if (isCover) { // case 2.2中的a ((JoinNode) node).setJoinStrategy(JoinStrategy.INDEX_NEST_LOOP); } else { // case 2,因为2.1与2.2现在使用同一种策略,就是使用NestLoop... ((JoinNode) node).setJoinStrategy(JoinStrategy.NEST_LOOP_JOIN); } } } else { // 这种情况也属于case 2,先使用NestLoop... ((JoinNode) node).setJoinStrategy(JoinStrategy.NEST_LOOP_JOIN); } // 将未能下推的条件加到result filter中 ((JoinNode) node).setResultFilter(FilterUtils.and(node.getResultFilter(), node.getWhereFilter())); return node; } else { // 将未能下推的条件加到result filter中 node.setResultFilter(FilterUtils.and(node.getResultFilter(), node.getWhereFilter())); return node; } } /** * <pre> * 选择sort merge join的条件: * 1. 存在orderby,orderby顺序包含所有join列或者join列包含所有orderby字段,一个前缀顺序匹配. (orderby必须按顺序匹配,join列可以无序) * 2. 存在groupby, groupby中包含所有join列,或者join列包含所有groupby,(group by和join列都可以无序) * </pre> */ private static boolean canChooseSortMerge(JoinNode node) { List<ISelectable> columns = node.isLeftOuterJoin() ? node.getRightKeys() : node.getLeftKeys(); // 先判断group,判断排序条件是否满足 // 再判断order return match(node.getGroupBys(), columns, false) || match(node.getImplicitOrderBys(), columns, true); } private static boolean match(List<IOrderBy> orderBys, List<ISelectable> columns, boolean needOrder) { if (orderBys.isEmpty()) { return false; // 这种情况就用传统的模式 } if (needOrder) { for (int i = 0; i < orderBys.size(); i++) { IOrderBy order = orderBys.get(i); if (i >= columns.size()) { return true; // 代表前缀匹配成功 } boolean match = false; for (ISelectable column : columns) { if (order.getColumn().equals(column)) { match = true; break; } } if (!match) { return false; } } return true;// 走到这一步,代表匹配成功 } else { // 针对group by的情况,只要是一个包含关系,不需要顺序 for (int i = 0; i < columns.size(); i++) { ISelectable column = columns.get(i); if (i >= orderBys.size()) { return true; // 代表前缀匹配成功 } boolean match = false; for (IOrderBy order : orderBys) { if (order.getColumn().equals(column)) { match = true; break; } } if (!match) { return false; } } return true;// 走到这一步,代表匹配成功 } } private static void buildTableFilter(TableNode tableNode) { List<List<IFilter>> DNFNodes = FilterUtils.toDNFNodesArray(tableNode.getWhereFilter()); if (DNFNodes.size() == 1) { // 即使索引没有选择主键,但是有的filter依旧会在s主键上进行,作为valueFilter,所以要把主键也传进去 Map<FilterType, IFilter> filters = FilterSpliter.splitByIndex(DNFNodes.get(0), tableNode); tableNode.setKeyFilter(filters.get(FilterType.IndexQueryKeyFilter)); tableNode.setResultFilter(filters.get(FilterType.ResultFilter)); tableNode.setIndexQueryValueFilter(filters.get(FilterType.IndexQueryValueFilter)); } else { // 如果存在多个合取方式的的or组合时,无法区分出key/indexValue/result,直接下推 tableNode.setResultFilter(tableNode.getWhereFilter()); } } private static boolean isOptimizeJoinOrder(Map<String, Object> extraCmd) { return GeneralUtil.getExtraCmdBoolean(extraCmd, ExtraCmd.CHOOSE_JOIN, false); } }