【问题标题】:How do compiled queries in slick actually work?slick 中的编译查询实际上是如何工作的?
【发布时间】:2016-02-15 16:26:27
【问题描述】:

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

【问题讨论】:

  • 不是他们执行一次。编译一次。
  • 为什么需要多次编译?你熟悉准备好的语句吗?
  • @perdrofurla 是的!编辑了...你能给我一个清楚的解释吗?
  • 不是真的,我只有一个模糊的想法。不过我叫来了大师,他们说不定能有效回答。
  • 实际上我正在尝试理解一个精巧的示例slick.typesafe.com/doc/2.1.0/queries.html#compiled-queries,作为初学者,我无法理解使用编译查询背后的优势。

标签: scala slick compiled-query


【解决方案1】:

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

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

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

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

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

【讨论】:

    【解决方案2】:

    优势很容易解释:查询编译需要时间,无论是在 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 语句(或在您第一次使用时即时编译)并通过提取器线程化参数值来填充语句参数。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-02
    • 2021-01-21
    • 2011-09-27
    • 2021-12-16
    • 2013-03-14
    • 2021-03-23
    • 2011-02-11
    相关资源
    最近更新 更多