我正在寻找有关已编译查询执行的详细说明。我无法理解他们如何只编译一次以及他们使用背后的优势
答案 0 :(得分:13)
假设这个问题与使用有关,而不是编译查询的内部实现,这是我的答案:
当您编写Slick查询时,Slick实际上会在内部为所有涉及的表达式创建一个数据结构 - 一个抽象语法树(AST)。当您想要运行此查询时,Slick将获取数据结构并将其转换(或换句话说编译)为SQL字符串。这可能是一个相当耗时的过程,比在DB上实际执行快速SQL查询需要更多的时间。理想情况下,每次查询需要执行时,我们都不应该对SQL进行这种转换。但是如何避免呢?通过缓存已翻译/编译的SQL查询。
Slick可以做的事情就像第一次编译它并在下次缓存它。但它没有,因为这使得用户更难以推理Slick的执行时间,因为相同的代码将在第一次变慢,但之后会更快。 (Slick还需要在第二次运行时识别查询并在某个内部缓存中查找SQL,这会使实现复杂化。)
因此Slick每次都会编译查询,除非你明确地缓存它。这使得行为非常可预测并最终更容易。要对其进行缓存,您需要使用Compiled
并将结果存储在下次需要查询时不会重新计算的位置。所以使用像def
这样的def q1 = Compiled(...)
没有多大意义,因为它每次都会编译它。它应该是val
或lazy 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语句(或者在第一次使用它时动态编译它)并通过将参数值穿过提取器来填充语句参数。