关系型数据库工作原理-查询优化器之动态规划,贪婪算法和启发式算法(17)

本文翻译自Coding-Geek文章:《 How does a relational database work》。原文链接:http://coding-geek.com/how-databases-work/#Buffer-Replacement_strategies

本文翻译了如下章节, 介绍数据库查询优化器中寻找最优联表方案动态规划,贪婪算法和启发式算法:
《关系型数据库工作原理-查询优化器之动态规划,贪婪算法和启发式算法(17)》

动态规划、贪婪算法和启发式算法-Dynamic programming, greedy algorithm and heuristic

关系型数据库会尝试之前讲过的所有优化途径。对优化器来说,最重要的工作就是在有限的时间内找出最优的解决方案。

大部分时间,优化器不是找最优解而是找一个还过得去的解决方案。

对于小数据的查询,穷举强制找出最优策略是可行的。但是即使最一个中型数据量表的查询,也有避免大量无效的计算而使用穷举方式计算最优解的方法。这就是动态规划算法。

动态规划-Dynamic Programming

隐含在这两个单词背后的含义是许多执行策略都是非常类似的。参看下面几种策略:

它们都具有相同的子树(A JOIN B).因此不用每次都重复计算这个子树的成本,我们能够将计算结果保存起来,每次计算的时候重复利用它的结果。

更一般的讲,我们将处理一个重复计算的问题。为了避免额外的部分结果重复结算,我们使用缓存技术。

使用这种缓存计算,我们不需要计算(2*N)!/(N+1)!次,我们仅需要计算3的N次方次即可。在前一个样例中,连接4张表,这意味着计算的次数由336次降到了81次。如果有一个涉及8张表的大查询语句,计算次数由57657600次降到了6561次。

它的伪代码如下:

procedure findbestplan(S)
if (bestplan[S].cost infinite)
   return bestplan[S]
// else bestplan[S] has not been computed earlier, compute it now
if (S contains only 1 relation)
         set bestplan[S].plan and bestplan[S].cost based on the best way
         of accessing S  /* Using selections on S and indices on S */
     else for each non-empty subset S1 of S such that S1 != S
   P1= findbestplan(S1)
   P2= findbestplan(S - S1)
   A = best algorithm for joining results of P1 and P2
   cost = P1.cost + P2.cost + cost of A
   if cost < bestplan[S].cost
       bestplan[S].cost = cost
      bestplan[S].plan = “execute P1.plan; execute P2.plan;
                 join results of P1 and P2 using A”
return bestplan[S]

对一个大的查询操作,你仍然可以使用动态规划的方式,但可以使用一些额外的规则(或者启发式算法)来移除一些不必要的可选条件:
1. 如果我们仅仅分析一些确定类型的方案(例如只处理left-deep trees).就可以将计算次数由3的N次方降到n*2的n次方。
.
2. 如果我们添加一些逻辑规则来排除某些模式的方案(例如,在给定的连接条件中某张表有索引,那么除非基于索引连接否则不要使用归并连接方法)。这样能建少组合次数又不会将最优解排除掉。
.
3. 如果我们能添加一些规则影响执行流程(顺序)(例如:最先执行指定的连接操作),这也能减少很多的组合次数。
.
4. …

贪婪算法-Greedy algorithms

针对一个非常大的查询(关联的表很多)或者想到快速的得到计算结果,另外一种算法经常被使用,即贪婪算法。

其基本思想是创建一个用增量的方式构建查询方案。按这个原则,贪婪算法能一次性找出方案的最优解。

这个算法第一步先加入一个join操作,然后用同样的规则逐步加入其它的join操作。

我们来看一个简单的例子。我们看一下之前举的4个join基于5张表的查询操作(A,B,C,D and E)。为简化问题,我们仅使用循环嵌套连接。我们使用的规则是“使用连接成本的连接”。
1. 我们从5张表中任意一张表开始(就选A表吧)。
2. 我们计算每一个和A表连接的成本(A可以是内连接对象,也可以是外连接对象)。
3. 我们找到A与B表的连接是成本最低的。
4. 然后我们可以计算所有与AB表连接结果的连接成本(AB表连接结果可以作为内连接对象或者外连接对象)。.
5. 我们发现(A JOIN B) JOIN C拥有最低的成本。
6. 然后我们计算所有与(A JOIN B) JOIN C结果的连接成本。依次类推。
7. ….
8. 最终我们找到一个成本最优的连接方案(((A JOIN B) JOIN C) JOIN D) JOIN E)。
9. 因为我们是随机选择A表作为开始,我们也可以选B,C,D,E开始,最终得到一个成本最优的方案。

顺便说一下,这个算法有另外一个名称:最近邻居算法。

我不再输入细节的讲,建立一个好的模型,排好序(N*logN)问题就容易解决了。贪婪算法的时间复杂度是O(N*log(N)) , 相对于动态规划的 O(3N)的来说,这种方案不要太好。如果有一个涉及20张表的连接查询,它意味着26次计算与3486784401次计算的差异。

贪婪算法的问题是我们假设找两表之间最优成本的连接方案,再通过不断加入新表的方式,最终得到的就是一个最优的方案。但(译者注:局部最优不等于全局最优):
1. 即使A JOIN B在AB、AC里面是最优的。
2. (A JOIN C) JOIN B也可能是比(A JOIN B) JOIN C更优的结果。

为了改进这个算法结果(译者注:注意是改进,无法彻底解决),我们可以使用不同的规则执行多次贪婪算法,保留最好的方法。

其它算法–Other algorithms
【如果你已经厌烦算法,你可以跳过这个章节,下面讲的内容不是很关键】。
寻找最优解在计算机研究领域是个热门话题。他们经常会尝试对一些更具体的问题或者模型找出更好的方案。例如:
1. 如果查询语句是一个星型连接(多表连接的一种),一些数据库会使用特殊算法。
2. 如果查询是一个并行查询语句,一些数据库也会采用特殊处理算法。
3. …

人们也在研究其它能代替动态规划的算法,在大查询语句中。贪婪算法属于启发式算法家族中的一员。

贪婪算法遵循一条规则,保留上一步算法找到的结果,并尝试将它追加到当前这一步找到的方案里面。

一些算法遵循这个原则,但在执行过程中并不保证上一步是使用的最优解决。这些算法被称为探索算法(译者注:如熟知的模拟退火算法,基因遗传算法)。

例如:基因遗传算法遵循这样一个原则,按不保证上一步总是最优的结果:
1. 一种方案代表了一种可行的完整查询计划。
2. 代替(贪婪算法中)保存一种最优解决方案,基因遗传算法在每一步计算的时候保存P种结果方案。
3. P总查询计划是随机选择的。
4. 仅拥有最优成本的计划会被保留。
5. 所有最优的计划合并在一起产生新的P种计划。
6. P种新计划会被随机修改.
7. 前面3不重复执行T次。
8. 保留最后一次迭代(P种计划里面)的最优解。

迭代的次数越多,你得的方案就越好。
是不是很神奇?不,它是自然法则:适者生存。

FYI,基因遗传算法在PostgreSQL中实现了,但是我没有找到该算法是否会默认使用。

在数据库中还使用了其它一些启发式算法,如模拟退火,迭代改进,两阶段优化等。但是,我不清楚这些算法是否在企业级数据库中使用,或者他们仅在学术研究的数据库中用到。

更多算法相关的信息,你可以阅读如下文章,它里面介绍了更多可行的算法: Review of Algorithms for the Join Ordering Problem in Database Query Optimization

    原文作者:启发式算法
    原文地址: https://blog.csdn.net/ylforever/article/details/79872774
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞