14

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

4

2 回答 2

19

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

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

Slick 可以做一些事情,比如只在第一次编译它并为下一次缓存它。但事实并非如此,因为这使用户更难推断 Slick 的执行时间,因为相同的代码第一次会很慢,但以后会更快。(Slick 还需要在第二次运行时识别查询并在一些内部缓存中查找 SQL,这会使实现复杂化)。

因此,除非您明确缓存它,否则 Slick 每次都会编译查询。这使得行为非常可预测并且最终更容易。要缓存它,您需要使用Compiled并将结果存储在下次需要查询时不会重新计算的地方。所以使用deflikedef q1 = Compiled(...)没有多大意义,因为它每次都会编译它。它应该是一个vallazy val。此外,您可能不想将该 val 放入您多次实例化的类中。相反,一个好的地方是val在顶级 Scala 单例object中,它只计算一次并在 JVM 的生命周期内保存。

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

于 2016-02-15T17:18:50.027 回答
11

优点很容易解释:查询编译需要时间,无论是在 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,它们也可以是嵌套元组、HLists 或任何自定义类型。(我们仍然为所有函数参数提供抽象,因为在语法上写 aFunction10比写Function1aTuple10作为参数的 a 更方便。)

有一种方法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 语句(或在您第一次使用时即时编译),并通过提取器线程化参数值来填充语句参数。

于 2016-02-15T17:36:39.853 回答