如何在光滑的编译查询实际工作?

时间:2016-02-15 16:26:27

标签: scala slick compiled-query

我正在寻找有关已编译查询执行的详细说明。我无法理解他们如何只编译一次以及他们使用背后的优势

2 个答案:

答案 0 :(得分:13)

假设这个问题与使用有关,而不是编译查询的内部实现,这是我的答案:

当您编写Slick查询时,Slick实际上会在内部为所有涉及的表达式创建一个数据结构 - 一个抽象语法树(AST)。当您想要运行此查询时,Slick将获取数据结构并将其转换(或换句话说编译)为SQL字符串。这可能是一个相当耗时的过程,比在DB上实际执行快速SQL查询需要更多的时间。理想情况下,每次查询需要执行时,我们都不应该对SQL进行这种转换。但是如何避免呢?通过缓存已翻译/编译的SQL查询。

Slick可以做的事情就像第一次编译它并在下次缓存它。但它没有,因为这使得用户更难以推理Slick的执行时间,因为相同的代码将在第一次变慢,但之后会更快。 (Slick还需要在第二次运行时识别查询并在某个内部缓存中查找SQL,这会使实现复杂化。)

因此Slick每次都会编译查询,除非你明确地缓存它。这使得行为非常可预测并最终更容易。要对其进行缓存,您需要使用Compiled并将结果存储在下次需要查询时不会重新计算的位置。所以使用像def这样的def q1 = Compiled(...)没有多大意义,因为它每次都会编译它。它应该是vallazy val。此外,您可能不希望将该val放入多次实例化的类中。一个好的地方是顶级Scala单例val中的object,它只计算一次并保留JVM的实时时间。

所以换句话说,Compiled没有任何神奇之处。它只允许您显式触发Slick的Scala-to-SQL编译并返回包含SQL的值。重要的是,这允许与实际执行查询分开触发编译,这允许您编译一次,但多次运行它。

答案 1 :(得分:7)

优点很容易解释:查询编译在Slick和数据库服务器中都需要时间。如果多次执行相同的查询,则只编译一次会更快。

Slick需要将带有集合操作的AST编译成SQL语句。 (实际上,如果没有编译查询,你必须首先构建 AST,但与编译时相比,这非常快。)

数据库服务器必须为查询构建执行计划。这意味着解析查询,将其转换为本机数据库操作,并根据数据布局(例如,使用哪个索引)查找优化。即使您不在Slick中使用编译查询,只需使用绑定变量,也可以避免使用此部分,以便始终为不同的参数集获取相同的SQL代码。数据库服务器保留最近使用/编译的执行计划的缓存,因此只要SQL语句相同,执行计划就只是哈希查找,不需要再次计算。 Slick依赖于这种缓存。 Slick没有直接与数据库服务器通信以重用旧查询。

至于如何实现它们,以同样的方式处理流/非流和编译/应用/即席查询有一些额外的复杂性,但有趣的切入点在Compiled

implicit def function1IsCompilable[A , B <: Rep[_], P, U](implicit ashape: Shape[ColumnsShapeLevel, A, P, A], pshape: Shape[ColumnsShapeLevel, P, P, _], bexe: Executable[B, U]): Compilable[A => B, CompiledFunction[A => B, A , P, B, U]] = new Compilable[A => B, CompiledFunction[A => B, A, P, B, U]] {
  def compiled(raw: A => B, profile: BasicProfile) =
    new CompiledFunction[A => B, A, P, B, U](raw, identity[A => B], pshape.asInstanceOf[Shape[ColumnsShapeLevel, P, P, A]], profile)
}

这为每个Compilable提供了一个隐含的Function对象。对于灵魂2至22的类似方法是自动生成的。由于各个参数只需要Shape,因此它们也可以是嵌套元组,HList或任何自定义类型。 (我们仍然提供所有函数arities的抽象,因为它在语法上更方便编写,比如Function10而不是以Function1作为参数的Tuple10。)

Shape中有一个只存在支持编译函数的方法:

/** Build a packed representation containing QueryParameters that can extract
  * data from the unpacked representation later.
  * This method is not available for shapes where Mixed and Unpacked are
  * different types. */
def buildParams(extract: Any => Unpacked): Packed

此方法构建的“packed”表示可以生成包含具有正确类型的QueryParameter个节点的AST。在编译期间,它们与其他文字处理相同,但实际值未知。提取器在顶层以identity开始,并根据需要进行细化以提取记录元素。例如,如果你有一个Tuple2参数,那么AST最终会有两个QueryParameter个节点,它们知道如何在以后提取元组的第一个和第二个参数。

此后一点是编译后的查询应用。执行这样的AppliedCompiledFunction使用预编译的SQL语句(或者在第一次使用它时动态编译它)并通过将参数值穿过提取器来填充语句参数。