数据库的查询引擎[Query Engines]有两种查询方式,Push-based和Pull-based。

一般来说,一个Query的执行依赖于Query Plan,而Query Plan是一个树状图[Tree-based Graph]。该图的叶子节点靠近原始数据,而根结点靠近最后结果。图中的每个结点是一个operator来具体执行特定操作。

Pull-based Query Execution

Pull-based Query Execution一般称火山模型[Volcano Model]或者迭代器模型[Iterator Model]。该模型是最经典的查询执行模型,也被广泛应用。在Query Plan中,Pull-based模型的执行过程由根结点发起,通过不断的向其子结点调用next()方法来获得其子几点的下一个结果,然后该子结点再调用其子结点,如此迭代,直到叶子节点调用原始数据。Pull-based Query Execution执行过程中,很多结点也就是operator可能处于空闲状态直到其父结点调用next()

Push-based Query Execution

Push-based Query Execution相对于Pull-based Query Execution正好反过来。查询执行过程由叶子结点发起,其首先读取数据,然后向其父结点传递结果,由于没有像Pull-based Query Execution的next()方法来触发数据传递,所以一般需要一个调度器[Scheduler]来决定子结点何时向父结点传递什么数据,所以如果调度得当整个query execution会执行的更有效率。但是因为结点之间的数据传递不是实时的,每个结点可能都需要一个buffer来保存其处理结果直到传给父结点,这就带来了额外的存储开销。

Pull-based vs Push-based

总体来说,这两种执行方式并没有明确的优劣之分,但是他们确实有各自适应的场景。比如,如果是streaming data system,就只能使用push-based query execution,如果使用pull-based,就需要类似materialization的机制来缓存两次甚至多次pull之间的数据。反过来说,如果数据不经常变化,那么可能使用pull-based的方法就是更高效的方式来处理不畅发生的数据变化。

另外一个区别在于,push-based可能在有很多data reuse的场景表现更好。比如,如果一个任务的执行过程类似一个DAG,最底层的数据扫描结点的结果需要传递给多个上层结点,那么push-based只需要执行一次就可以[如果数据可以一直存在内存中],多个结点可以共享读取的数据,但是pull-based就相当于两个结点都会pull底层结点的结果,哪怕他们的是相同的。