再谈选择率 Selectivity

     查询优化中一个重要的工作是选择一个最优(或者次优)的候选解(通常是查询计划)作为我们的最优解,即我们通常所说的cost-based optimization(CBO)。 如何比较两个候选解的优劣?通过对两个候选解的代价计算,如果对于一个候选解CandidateA其代价为1个单位,另外一个候选解CandidateB的代价为0.5个单位,那么我们称之为CandidateA的解劣于CandidateB;例如:cost_function(CandidateA) < const_function(CandidateB)。当然这是在限定我们的代价函数const_function为一个单目标函数;当我们的代价计算函数为一个多目标函数时候,我们便无法通过上述的简单的比较来,而是要满足一组约束条件。此时我们将在该目标空间内所获得的最优解称为Pareto最优,此时的问题变演化为多目标的优化问题(Multi-Objective Optimization, MOP)。当然在当今的数据库系统中我们的代价函数还仅仅是单目标函数。cost_function = cpu_cost + io_cost;即我们追求cpu计算代价和io的整体代价最优,而非是cpu的代价和io的代价分别是最优,即不是满足如下的条件:
 {
     min (cpu_const_function) ;
     min (io_cost_function) ;
  }
      其中,cpu_function和io_cost_function分别为cpu代价计算函数和io代价计算函数。但是当我们处于分布式系统下我们还需要考虑网络的开销,而一个不好实现同样会造成大量的数据迁移,而这又涉及到大量的数据读写和网络传输,当然如果不将中间结果进行持久化,那么必然需要大量的内存来对这些中间结果缓存,当然这些需要进行权衡。
      对于当前基于文件系统的数据库系统来说,数据的存取均会涉及大大量的io操作(虽然当代的数据库实现中均有各种的cache和buffer),而这些io操作又由磁盘来完成具体的操作,当然ssd的出现改变了我们对于传统磁盘访问的理解。在顺序访问和随访访问一条记录所需的io代价一定时候,一个查询计划的io代价就与我们所获取的记录数量(The Number of Tuples)成正比关系。这也是我们能够在查询语句真正执行之前能够确定以何种方式执行效率最高的基础。
      在而以前的文章中我们讨论过,在计算代价的时候我需要使用该条件所对应的元组数量,但是在不对整个表进行全部扫描或者索引扫描的条件下,我们使用的是该条件所对应的选择率来描述满足该条件的元组数量,而选择率则是一个基于统计的概率意义下的值,而非一个确切的描述该条件下元组的情况。在以前的博文中我们阐述了如果获得一个条件所对应的选择率。但是由于我们在数据库在实际的使用过程中会发生大量的更新操作(插入,删除,更新等等)以及vaccum的使用使得Pg_stats等元数据表中的数据无法正确的描述当前数据库的真实情况;此时我们需要运行analyze命令来重新更新元数据表中的数据。虽然可以通过analyze命令来更新元数据表中的数据,但是我们仍然无法准确的掌握当时数据库的实时状态,如果过想实时掌握这些信息,我们将花费巨大的精力来维护这些统计信息,而我们需要这些统计信息的作用是来对候选查询计划的代价进行计算,从而决定我们使用哪个候选解作为最优解,而非对每个候选解给出精确的代价。
     在极端的情况下,由于选择率的误差造成的查询计划的变化不乏例子,例如:在该文章中就讨论了由于选择率的变化导致的查询计划的不同选择[…]
     因此,这就必须要我们能够给出一个在各种情况下具有稳定性的选择率的计算方法。如何使用我们已有的统计知识来构建较为精确的选择率计算模型,这也以后的工作方面吧。下面的文章就是解决如何获得较为准确的值。[…]
     当然已经有人试图来解决上述的问题了,且已将其给出的解决方案经提交到PostgreSQL的commitfest里了,https://commitfest.postgresql.org/8/434/ 。大家有兴趣的可以去看看。