openGauss源码解析(101)
openGauss源码解析:SQL引擎源解析(16)
6.3.5 遗传算法
遗传算法(genetic algorithm,GA)作为进化算法的一种,借鉴了达尔文生物进化论中的自然选择以及遗传学机理。通过模拟大自然中“物竞天择,适者生存”这种进化过程来生成最优的个体。
当生成一定数量的原始个体后,可以通过基因的排列组合产生新的染色体,再通过染色体的杂交和变异获得下一代染色体。为了筛选出优秀的染色体,需要通过建立适应度函数计算出适应度的值,从而将适应度低的染色体淘汰。如此,通过个体间不断的遗传、突变,逐渐进化出最优秀的个体。将这个过程代入解决问题,个体即为问题的解。通过遗传算法,可以通过此类代际遗传来使得问题的解收敛于最优解。
区别于动态规划将问题分解成若干独立子问题求解的方法,遗传算法是一个选择的过程,它通过将染色体杂交构建新染色体的方法增大解空间,并在解空间中随时通过适应度函数进行筛选,推举良好基因,淘汰掉不良的基因。这就使得遗传算法获得的解不会像动态规划一样,一定是全局最优解,但可以通过改进杂交和变异的方式,来争取尽量的靠近全局最优解。
得益于在多表连接中的效率优势,在openGauss数据库中,遗传算法是动态规划方法的有益补充。只有在Enable_geqo参数打开,并且待连接的RelOptInfo的数量超过Geqo_threshold(默认12个)的情况下,才会使用遗传算法。
遗传算法的实现有下面5个步骤。
(1) 种群初始化:对基因进行编码,并通过对基因进行随机的排列组合,生成多个染色体,这些染色体构成一个新的种群。另外,在生成染色体的过程中同时计算染色体的适应度。
(2) 选择染色体:通过随机选择(实际上通过基于概率的随机数生成算法,这样能倾向于选择出优秀的染色体),选择出用于交叉和变异的染色体。
(3) 交叉操作:染色体进行交叉,产生新的染色体并加入种群。
(4) 变异操作:对染色体进行变异操作,产生新的染色体并加入种群。
(5) 适应度计算:对不良的染色体进行淘汰。
举个例子,如果用遗传算法解决货郎问题(TSP),则可以将城市作为基因,走遍各个城市的路径作为染色体,路径的总长度作为适应度,适应度函数负责筛选掉比较长的路径,保留较短的路径。算法的步骤如下。
(1) 种群初始化:对各个城市进行编号,将各个城市根据编号进行排列组合,生成多条新的路径(染色体),然后根据各城市间的距离计算整体路径长度(适应度),多条新路径构成一个种群。
(2) 选择染色体:选择两个路径进行交叉(需要注意交叉生成新染色体中不能重复出现同一个城市),对交叉操作产生的新路径计算路径长度。
(3) 变异操作:随机选择染色体进行变异(通常方法是交换城市在路径中的位置),对变异操作后的新路径计算路径长度。
(4) 适应度计算:对种群中所有路径进行基于路径长度由小到大排序,淘汰掉排名靠后的路径。
openGauss数据库的遗传算法正是模拟了解决货郎问题的方法,将RelOptInfo作为基因、最终生成的连接树作为染色体、连接树的总代价作为适应度,适应度函数则是基于路径的代价进行筛选,但是openGauss数据库的连接路径的搜索和货郎问题的路径搜索略有不同,货郎问题不存在路径不通的问题,两个城市之间是相通的,可以计算任何两个城市之间的距离,而数据库中由于连接条件的限制,可能两个表无法正常连接,或者整个连接树都无法生成。另外,需要注意的是,openGauss数据库的基因算法实现方式和通常的遗传算法略有不同,在于其没有变异的过程,只通过交叉产生新的染色体。
openGauss数据库遗传算法的总入口是geqo函数,输入参数为root(查询优化的上下文信息)、number_of_rels(要进行连接的RelOptInfo的数量)、initial_rels(所有的基表)。