优势很容易解释:查询编译需要时间,无论是在 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)
}
这为每个Function 提供了一个隐含的Compilable 对象。用于 2 到 22 的类似方法是自动生成的。因为单个参数只需要一个Shape,它们也可以是嵌套元组、HLists 或任何自定义类型。 (我们仍然为所有函数提供抽象,因为在语法上写一个Function10 比一个以Tuple10 作为参数的Function1 更方便。)
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
通过这种方法构建的“打包”表示可以生成一个包含 QueryParameter 节点且类型正确的 AST。它们在编译期间被视为与其他文字相同,除了实际值未知。提取器在顶层以identity 开头,并经过细化以根据需要提取记录元素。例如,如果您有一个 Tuple2 参数,则 AST 将最终得到两个 QueryParameter 节点,它们知道如何在稍后提取元组的第一个和第二个参数。
稍后是当编译的查询被应用时。执行这样的AppliedCompiledFunction 使用预编译的 SQL 语句(或在您第一次使用时即时编译)并通过提取器线程化参数值来填充语句参数。