2016-02-15 2 views
7

Я ищу подробное объяснение о выполнении скомпилированных запросов. Я не могу понять, как они просто скомпилировать один раз, и преимущество за их использованиеКак скомпилированные запросы в slick действительно работают?

+0

Разве это не то, что они выполняются один раз. Он компилируется один раз. – pedrofurla

+0

Зачем их собирать более одного раза? И вы знакомы с подготовленными заявлениями? – pedrofurla

+0

@perdrofurla yep! отредактированный ... Можете ли вы дать мне четкое объяснение? – imen

ответ

10

Если предположить, что этот вопрос об использовании, а не внутренняя реализация Составитель запросов, вот мой ответ:

Когда вы пишете Slick query, Slick фактически создает структуру данных внутри всех задействованных выражений - абстрактное синтаксическое дерево (AST). Когда вы хотите запустить этот запрос, Slick берет структуру данных и переводит (или, другими словами, компилирует) ее в строку SQL. Это может быть довольно продолжительный процесс, занимающий больше времени, чем выполнение быстрых SQL-запросов в БД. Поэтому в идеале мы не должны делать этот перевод на SQL каждый раз, когда запрос должен быть выполнен. Но как этого избежать? Кэширование переведенного/скомпилированного SQL-запроса.

Слик может сделать что-то вроде компиляции в первый раз и кэшировать его в следующий раз. Но это не так, потому что это затрудняет пользователю рассуждать о времени выполнения Slick, потому что тот же самый код будет медленным в первый раз, но быстрее позже. (Также Slick должен будет распознавать запросы, когда они будут выполняться во второй раз и искать SQL в некотором внутреннем кеше, что усложняло бы реализацию).

Таким образом, вместо этого Slick компилирует запрос каждый раз, если вы явно не кэшируете его. Это делает поведение очень предсказуемым и в конечном итоге проще. Чтобы кэшировать его, вам нужно использовать Compiled и сохранить результат в месте, которое НЕ будет пересчитано при следующем запросе. Поэтому использование def, как def q1 = Compiled(...), не имеет большого смысла, поскольку оно будет скомпилировать его каждый раз. Это должно быть val или lazy val. Также вы, вероятно, не хотите ставить этот val в класс, который вы создаете несколько раз. Хорошим местом вместо этого является val в одном слоте Scala высшего уровня object, который вычисляется только один раз и сохраняется в режиме реального времени JVM.

Таким образом, Compiled ничего не делает волшебным. Это позволяет вам явно вызывать компиляцию Scala-to-SQL Slick и возвращать значение, содержащее SQL. Важно отметить, что это позволяет инициировать компиляцию отдельно от фактического выполнения запроса, что позволяет вам компилировать один раз, но запускать его несколько раз.

+0

Спасибо за объяснение !!! – Sky

5

Достоинство легко объяснить: для компиляции запросов требуется время, как в Slick, так и на сервере базы данных. Если вы выполняете один и тот же запрос много раз, его быстрее компилировать только один раз.

Слик должен собирать АСТ с операциями коллекции в оператор SQL. (На самом деле, без скомпилированных запросов вы всегда должны до построить AST, но по сравнению со временем компиляции это очень быстро.)

Сервер базы данных должен построить план выполнения запроса. Это означает синтаксический анализ запроса, перевод его на собственные операции с базой данных и поиск оптимизаций на основе макета данных (например, какой индекс использовать). Эту часть можно избежать, даже если вы не используете скомпилированные запросы в Slick, просто используя переменные связывания, чтобы всегда иметь одинаковый код SQL для разных наборов параметров. Сервер базы данных хранит кеш недавно использованных/скомпилированных планов выполнения, поэтому, пока оператор SQL идентичен, план выполнения является только хеш-поиском и его не нужно снова вычислять. Слик полагается на такое кэширование. Прямой связи от 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 или любым настраиваемым типом. (Мы по-прежнему обеспечиваем абстракции для всех функций арность, потому что это синтаксический более удобно писать, скажем, 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 

«упаковано» представление, построенное этим способом можно производить AST, содержащий QueryParameter узлов с правильным типом. Во время компиляции они обрабатываются так же, как и другие литералы, за исключением того, что фактические значения неизвестны. Экстрактор запускается как identity на верхнем уровне и уточняется для извлечения элементов записи по мере необходимости. Например, если у вас есть параметр Tuple2, AST закончится двумя узлами QueryParameter, которые знают, как извлечь первый и второй параметры кортежа в более позднюю точку.

Это более поздний момент, когда скомпилированный запрос - - применяется. Выполнение такого AppliedCompiledFunction использует предварительно скомпилированный оператор SQL (или компилирует его «на лету», когда вы его используете в первый раз), и заполняет параметры оператора путем потоковой передачи значения аргумента через экстракторы.

+1

большое спасибо за ваш ответ. Теперь я понимаю полезность использования скомпилированных запросов, но я не могу попробовать их в примере. Не могли бы вы привести конкретный пример. спасибо – imen

+1

Здесь вы можете найти множество примеров: https://github.com/slick/slick/blob/master/slick-testkit/src/main/scala/com/typesafe/slick/testkit/tests/TemplateTest.scala – szeiger

Смежные вопросы