Избранное | Русский | Войти

Запросы и индексы

Каждый запрос к хранилищу данных использует индекс – таблицу, которая содержит результаты запроса, расположенные в необходимом порядке. Приложение App Engine определяет свои индексы в файле конфигурации datastore-indexes.xml. Веб-сервер разработки может автоматически создавать записи для этого файла, когда сталкивается с запросами, индексы для которых еще не настроены.

Механизм запросов, основанных на индексах, поддерживает наиболее распространенные типы запросов, но может не поддерживать некоторые типы запросов из других технологий баз данных, которые могут быть вам привычны. Ограничения, применяемые к запросам, вместе с соответствующими пояснениями перечислены ниже.

Запросы: введение

Запрос извлекает из хранилища данных объекты, удовлетворяющие заданному набору условий. В запросе указывается тип объекта, ноль или более условий, описывающих значения свойств объекта и иногда называемых "фильтрами", а также ноль или более описаний порядков сортировки. При выполнении запрос извлекает все объекты указанного типа, удовлетворяющие указанным условиям и отсортированные в заданном порядке.

JDO может выполнять запрашивать объекты, соответствующие определенным критериям. Также можно использовать Extent JDO для представления набора всех объектов определенного типа (все сохраненные объекты класса).

Запросы с JDOQL

JDO включает язык запросов для получения объектов, соответствующих установленным критериям. Этот язык, называющийся JDOQL, использует поля и классы данных JDO напрямую и включает проверку типов для параметров и результатов запросов. JDOQL подобен SQL, но более подходит для объектно-ориентированных баз данных, таких как хранилище данных App Engine. (Хранилище данных App Engine не поддерживает запросы SQL с интерфейсом JDO.)

API запроса поддерживает несколько способов вызова. Можно указать весь запрос в строке, используя синтаксис строки JDOQL. Также можно указать некоторые или все части запроса путем вызова методов для объекта запроса.

Ниже приведен простой пример запроса, использующего вызов с помощью метода, с одним фильтром и одним порядком сортировки, а также подстановкой параметра для значения в фильтре. Метод execute() объекта Query вызывается со значениями для замены в запросе в порядке их объявления.

import java.util.List;
import javax.jdo.Query;

// ...

    Query query = pm.newQuery(Employee.class);
    query.setFilter("lastName == lastNameParam");
    query.setOrdering("hireDate desc");
    query.declareParameters("String lastNameParam");

    try {
        List<Employee> results = (List<Employee>) query.execute("Smith");
        if (results.iterator().hasNext()) {
            for (Employee e : results) {
                // ...
            }
        } else {
            // ... no results ...
        }
    } finally {
        query.closeAll();
    }

Ниже приведен этот же запроса, использующий синтаксис строки:

    Query query = pm.newQuery("select from Employee " +
                              "where lastName == lastNameParam " +
                              "order by hireDate desc " +
                              "parameters String lastNameParam")

    List<Employee> results = (List<Employee>) query.execute("Smith");

Можно сочетать эти способы определения запроса. Например:

    Query query = pm.newQuery(Employee.class,
                              "lastName == lastNameParam order by hireDate desc");
    query.declareParameters("String lastNameParam");

    List<Employee> results = (List<Employee>) query.execute("Smith");

Можно повторно использовать один экземпляр Query с различными значениями, замененными на параметры путем вызова метода execute() несколько раз. Каждый вызов выполняет запрос и возвращает результаты в виде набора.

Синтаксис строки JDOQL поддерживает константы значений со строкой для строковых и числовых значений. Заключите строки в одинарные (') или двойные кавычки ("). Все остальные типы значений должны использовать подстановку параметров. Ниже приведен пример использования значения строкового литерала:

    Query query = pm.newQuery(Employee.class,
                              "lastName == 'Smith' order by hireDate desc");

Фильтры запросов

Фильтр указывает название поля, оператор и значение. Значение должно быть предоставлено приложением; оно не может ссылаться на другое свойство или быть вычислено через другие свойства. Оператор может быть одним из следующих:< <= == >= >

Примечание. Интерфейс хранилища данных Java не поддерживает операторы фильтра != и IN, реализованные в интерфейсе хранилища данных Python. (В интерфейсе Python эти операции реализованы в библиотеках на стороне клиента как несколько запросов к хранилищу данных; они не являются функциями самого хранилища.)

Объектом фильтра может быть любое поле объекта, включая первичный ключ и родителя группы объектов (см. Транзакции).

Чтобы являться результатом, объект должен соответствовать всем фильтрам. В синтаксисе строки JDOQL несколько фильтров разделяются знаком && (логическое "и"). Другие логические сочетания фильтров (логическое "или", "нет") не поддерживаются.

Из-за способа выполнения запросов хранилищем данных App Engine одиночный запрос не может использовать фильтры неравенства (< <= >= >) для нескольких свойств. Несколько фильтров неравенства для одного свойства (например, запрос диапазона значений) разрешены. См. раздел Ограничения, применяемые к запросам ниже.

    query.setFilter("lastName == 'Smith' && hireDate > hireDateMinimum");
    query.declareParameters("Date hireDateMinimum");

Порядки сортировки запросов

Порядок сортировки указывает свойство и направление, по возрастанию или по убыванию. Результаты возвращаются отсортированными в определенных порядках в порядке, в котором они были указаны. Если для запроса не указаны порядки сортировки, результаты сортируются по ключам объектов.

Из-за способа выполнения запросов хранилищем данных App Engine, если запрос указывает фильтры неравенства для свойства и порядки сортировки для других свойств, свойство, использованное с фильтрами неравенства, должно быть отсортировано до других свойств. См. раздел Ограничения, применяемые к запросам ниже.

    query.setOrdering("hireDate desc, firstName asc");

Диапазоны запросов

Запрос может указывать диапазон результатов, которые должны быть возвращены приложению. Диапазон определяет первый и последний результаты в полном наборе результатов, которые должны быть возвращены, используя цифровые индексы, начиная с 0 для первого результата. Например, диапазон 5, 10 возвращает 6-й, 7-й, 8-й, 9-й и 10-й результаты.

Начальное смещение влияет на производительность: хранилище данных должно получить, а затем удалить все результаты до начального смещения. Например, запрос с диапазоном 5, 10 получает 10 результатов из хранилища данных, затем удаляет первые 5 и возвращает оставшиеся 5 результатов приложению.

    query.setRange(5, 10);

Класс Extent

Extent JDO представляет все объекты в хранилище данных определенного класса

Можно начать Extent используя метод getExtent() PersistenceManager, передавая ему класс данных. Класс Extent реализует итеративный интерфейс для получения результатов. После получения результатов вызывается метод closeAll().

В следующем примере просматриваются все объекты Employee в хранилище данных:

import java.util.Iterator;
import javax.jdo.Extent;

// ...

    Extent extent = pm.getExtent(Employee.class, false);
    for (Employee e : extent) {
        // ...
    }
    extent.closeAll();

Extent получает результаты в пакетах и может превышать предел в 1000 результатов, применяемый для запросов.

Индексы: введение

Хранилище данных App Engine поддерживает индекс для каждого запроса, который собирается выполнить приложение. Как только приложение изменяет объекты хранилища данных, хранилище обновляет индексы, корректируя результаты. Когда приложение выполняет запрос, хранилище данных получает результаты напрямую из соответствующего индекса.

У приложения есть индекс для каждой комбинации типа, оператора фильтра, свойства фильтра и порядка сортировки, указанных в запросе. Рассмотрим запрос, написанный на JDOQL:

select from Person where lastName == "Smith"
                      && height < 72
                order by height desc

Индекс для этого запроса представляет собой таблицу ключей объектов типа Person, содержащую столбцы значений свойств height и lastName. Индекс отсортирован в убывающем порядке по свойству height.

Для двух запросов одинаковой формы, но с разными значениями фильтров используется один и тот же индекс. Например, для следующего запроса используется тот же индекс, что и для запроса выше:

select from Person where lastName == "Jones"
                      && height < 64
                order by height desc

Хранилище данных выполняет запрос следующим образом:

  1. Хранилище данных определяет индекс, который соответствует типу запроса, свойствам и операторам фильтров, а также порядкам сортировки.
  2. Хранилище данных начинает сканировать индекс с первого объекта, который удовлетворяет всем условиям фильтров, используя значения фильтров запроса.
  3. Хранилище данных продолжает сканировать индекс, возвращая все объекты до тех пор, пока не найдет следующий объект, не удовлетворяющий условиям фильтров. Этот процесс продолжается, пока не будет достигнут конец индекса или собрано максимально разрешенное количество результатов.

В таблице индекса содержатся столбцы для каждого свойства, используемого в фильтре или порядке сортировки. Строки отсортированы по следующим критериям в указанном порядке:

  • родители;
  • значения свойств, используемых в фильтрах равенства;
  • значения свойств, используемых в фильтрах неравенства;
  • значения свойств, используемых в порядках сортировки.

Это позволяет расположить результаты всевозможных запросов, использующих данный индекс, в последовательных строках таблицы.

Этот механизм поддерживает широкий спектр запросов и подходит для большинства приложений. Однако он не поддерживает некоторые типы запросов из других технологий баз данных, которые могут быть вам привычны.

Запрос не возвращает объекты, у которых нет свойства, включенного в фильтр

В индексе содержатся только объекты, обладающие всеми свойствами, на которые он ссылается. Если у объекта нет свойства, на которое ссылается индекс, этот объект не появится в индексе и не сможет быть результатом запроса, использующего данный индекс.

Обратите внимание, что хранилище данных App Engine различает объекты, которые не обладают свойством, и объекты, которые обладают свойством с нулевым значением (null). Чтобы каждый объект определенного типа мог стать потенциальным результатом выполнения запроса, можно использовать класс данных JDO или JPA, который всегда присваивает значения всем свойствам, соответствующим полям класса.

Текстовые и большие двоичные значения не индексируются

Свойства, имеющие значения типа Text (большие текстовые значения) или Blob (большие двоичные значения), не включаются в индексы и не обнаруживаются запросами.

Вследствие того, что такие значения свойств не включаются в индекс, запрос, содержащий фильтр или порядок сортировки по некоторому свойству, никогда не вернет объект, у которого это свойство имеет значение типа Text или Blob. Свойства с такими значениями обрабатываются так, как если бы их значения не были заданы относительно фильтров и порядков сортировки запроса.

Значения свойств, имеющие смешанный тип, упорядочиваются по типу

Если у двух объектов есть свойства с одинаковыми названиями, но разными типами значений, индекс этого свойства сортирует объекты сначала по типу значений, а затем в соответствующем каждому типу порядке. Например, если у двух объектов есть свойство "age", у одного с целочисленным значением, а у второго – со строковым, при сортировке по свойству "Age" объект с целочисленным значением всегда будет появляться прежде, чем объект со строковым, вне зависимости от самих значений.

Это особенно важно помнить при работе с целыми числами и числами с плавающей точкой, которые хранилище данных считает разными типами. Свойство с целым значением 38 после сортировки окажется раньше по порядку, чем значение с плавающей точкой 37.5, поскольку значения типа integer всегда выводятся раньше значений типа float.

(При работе с JDO или JPA такая ситуация может возникнуть только в том случае, если вы измените тип поля, не обновив существующие в хранилище данных объекты, или будете использовать низкоуровневый API хранилища данных либо API без поддержки Java.)

Определение индексов в файле конфигурации

App Engine создает индексы для ряда простых запросов по умолчанию. Индексы, необходимые для других запросов, приложение должно указывать в файле конфигурации datastore-indexes.xml. Если приложение, работающее под управлением App Engine, пытается выполнить запрос, для которого отсутствует соответствующий индекс (заданный по умолчанию или указанный в файле datastore-indexes.xml), то этот запрос не будет выполнен.

App Engine автоматически создает индексы для следующих форм запросов:

  • запросы, использующие только фильтры равенства и родителей;
  • запросы, использующие только фильтры неравенства (которые можно задать только для одного свойства);
  • запросы с единственным порядком сортировки по свойству (по возрастанию либо по убыванию).

Для других форм запросов индексы необходимо указывать в файле datastore-indexes.xml. К ним относятся:

  • запросы с несколькими порядками сортировки;
  • запросы с сортировкой по ключам в порядке убывания;
  • запросы с одним или несколькими фильтрами неравенства для некоторого свойства и одним или несколькими фильтрами равенства для других свойств;
  • запросы с фильтрами неравенства и родителей.

Веб-сервер разработки облегчает управление конфигурацией индексов: вместо отказа выполнить запрос, для которого требуется отсутствующий индекс, он может создать конфигурацию индекса, которая позволит успешно выполнить запрос. Если во время локального тестирования приложения вызываются все запросы, которые может выполнять приложение (все комбинации типа, родителя, фильтра и порядка сортировки), созданные записи будут представлять собой полный набор индексов. Если во время тестирования были выполнены не все возможные формы запросов, перед загрузкой приложения просмотрите и настройте конфигурацию индексов.

Можно определить индексы вручную, используя файл конфигурации datastore-indexes.xml в каталоге WEB-INF/ WAR приложения. Если включена автоматическая конфигурация индексов (см. ниже), сервер разработки создает конфигурацию индекса в файле datastore-indexes-auto.xml в каталоге WEB-INF/appengine-generated/ и использует оба файла для определения полного набора индексов.

Рассмотрите еще раз следующий пример запроса:

select from Person where lastName = 'Smith'
                      && height < 72
                   order by height desc

Конфигурация для индекса, необходимого для данного запроса, выглядит в datastore-indexes.xml следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes
  xmlns="http://appengine.google.com/ns/datastore-indexes/1.0"
  autoGenerate="true">
    <datastore-index kind="Person" ancestor="false">
        <property name="lastName" direction="asc" />
        <property name="height" direction="desc" />
    </datastore-index>
</datastore-indexes>

Если элемент <datastore-indexes> в datastore-indexes.xml имеет атрибут autoGenerate="true" (как выше), или если в приложении отсутствует файл datastore-indexes.xml, включена автоматическая конфигурация индекса. Если при включенной автоматической конфигурации индекса приложение выполняет этот запрос на сервере разработки, и отсутствует конфигурация для индекса, сервер добавляет эту конфигурацию индекса в файл datastore-indexes-auto.xml.

Дополнительную информацию о datastore-indexes.xml и datastore-indexes-auto.xml см. в статье Конфигурация индекса хранилища данных Java.

Запросы по ключам

Для ключей объектов может применяться фильтр запроса или порядок сортировки. В JDO ссылка на ключ объекта в запросе создается с помощью поля первичного ключа объекта. Хранилище данных получает полное значение ключа для таких запросов, включая путь родителя объекта, тип и назначенную приложением строку имени ключа или назначенный системой числовой идентификатор.

Поскольку ключ объекта уникален среди всех объектов в системе, запросы упрощают получение объектов определенного типа пакетами, например, пакетный дамп содержания хранилища данных. В отличие от диапазонов JDOQL это эффективно работает для любого количества объектов.

Упорядочивание ключей происходит в следующей очередности: по пути к родителю, по типу, а затем по идентификатору или названию ключа. Типы и названия ключей являются строками и упорядочиваются по байтовому значению. Идентификаторы являются целыми числами и упорядочиваются численно. Если объекты с одинаковым родителем и типом используют как строковые названия ключей, так и числовые идентификаторы, считается, что объекты с числовыми идентификаторами меньше, чем объекты со строковыми названиями ключей. Аналогично сравниваются и элементы пути к родителю: по типу (строка), затем по названию ключа (строка) или идентификатору (число).

Запросы, содержащие ключи, используют индексы так же, как запросы, содержащие свойства, с одним небольшим отличием: запрос с фильтром равенства по ключу, в котором есть и другие фильтры, должен использовать пользовательский индекс, определяемый в файле конфигурации индекса приложения. Как и для других запросов, при проверке запроса, которому требуется пользовательский индекс, веб-сервер разработки делает в этом файле соответствующие записи о конфигурации.

Ограничения, применяемые к запросам

Природа механизма запросов с использованием индексов налагает на действия запросов ряд ограничений.

Для фильтрации или сортировки по свойству требуется существование этого свойства

Условие фильтра запроса или порядок сортировки по свойству также требует, чтобы для данного свойства объекта было задано значение.

Объект хранилища данных необязательно должен иметь значение для свойства, для которого есть значения у других объектов того же типа. Фильтр по свойству применяется только к тем объектам, для которых значение этого свойства задано. Объекты, для которых значение свойства, используемого в фильтре или порядке сортировки, не задано, исключаются из индекса, создаваемого для запроса.

Невозможно отфильтровать объекты, у которых нет свойства

Выполнить запрос для объектов, у которых отсутствует заданное свойство, невозможно. Вариантом решения является создание фиксированного (смоделированного) свойства со значением по умолчанию null с последующей фильтрацией объектов со значением свойства null.

Можно использовать фильтры неравенства только по одному свойству

Запрос может использовать фильтры неравенства (<, <=, >=,>) только по одному свойству среди всех своих фильтров.

Например, следующий запрос допустим:

select from Person where birthYear >= minBirthYearParam
                      && birthYear <= maxBirthYearParam

Однако следующий запрос недопустим, так как он использует фильтры неравенства по двум различным свойствам одновременно:

select from Person where birthYear >= minBirthYearParam
                      && height >= minHeightParam   // ERROR

Фильтры могут сочетать операторы равенства (==) для различных свойств в одном запросе, в том числе в запросах с одним или несколькими условиями неравенства для свойства. Вот пример допустимого запроса:

select from Person where lastName == lastNameParam
                      && city == cityParam
                      && birthYear >= minBirthYearParam

Механизм запросов предполагает, что все результаты запроса расположены в таблице индекса подряд, во избежание необходимости сканировать всю таблицу результатов. Одна таблица индекса не может содержать несколько фильтров неравенства по разным свойствам, одновременно обеспечивая расположение результатов подряд.

Свойства в фильтрах неравенства должны быть отсортированы перед применением других порядков сортировки

Если запрос содержит фильтр неравенства и один или несколько порядков сортировки, в нем также должна выполняться сортировка по свойству, используемому в неравенстве, причем перед сортировкой по другим свойствам.

Следующий запрос недопустим, так как он использует фильтр неравенства и не выполняет сортировку по используемому в этом фильтре свойству:

select from Person where birthYear >= minBirthYearParam
                order by lastName                    // ERROR

Недопустим и следующий запрос, так как сортировка по используемому в фильтре свойству не выполняется перед сортировкой по другим свойствам:

select from Person where birthYear > minBirthYearParam
                order by lastName, birthYear         // ERROR

Следующий запрос допустим:

select from Person where birthYear >= minBirthYearParam
                order by birthYear, lastName

Чтобы получить результаты, соответствующие фильтру неравенства, запрос сканирует таблицу индекса начиная с первой подходящей строки и возвращает результаты до тех пор, пока не найдет строку, не удовлетворяющую условиям фильтра. Чтобы полный набор результатов был представлен в таблице подряд, ее строки должны быть отсортированы по фильтру неравенства перед применением других порядков сортировки.

Порядки сортировки и свойства с несколькими значениям

При индексации свойств, имеющих несколько значений, используется необычный порядок сортировки:

  • Если объекты сортируются по свойству с несколькими значениями в возрастающем порядке, для сортировки используется наименьшее из значений.
  • Если объекты сортируются по свойству с несколькими значениями в убывающем порядке, для сортировки используется наибольшее из значений.
  • Величина и количество остальных значений на сортировку не влияют.
  • При наличии связи ключ объекта используется для ее разрыва.

Такой порядок сортировки имеет необычное следствие: значения [1, 9] будут располагаться перед значениями [4, 5, 6, 7] и при сортировке по возрастанию, и по убыванию.

Следует сделать важное предупреждение относительно запросов, содержащих как фильтр равенства, так и порядок сортировки по свойству с несколькими значениями. В таких запросах порядок сортировки не учитывается. Для свойств с одним значением это обеспечивает простую оптимизацию. У всех результатов значение свойства будет одинаковым, поэтому необходимости дополнительно их сортировать нет.

Однако при наличии у свойства дополнительных значений могут возникать трудности. Так как указанная сортировка не применяется, результаты запроса могут возвращаться в другом порядке, чем необходимо. (Восстановление проигнорированного порядка сортировки требует лишних затрат и дополнительных индексов. Описанный случай возникает редко, поэтому планировщик запросов не рассматривает его.)

Большие объекты и разрастающиеся индексы

Как описано выше, каждое свойство (значение которого имеет тип, отличный от Text или Blob) каждого объекта добавляется по крайней мере в одну таблицу индекса, в том числе в простой индекс, задаваемый по умолчанию, и любые другие индексы, описанные в файле приложения datastore-indexes.xml и ссылающиеся на данное свойство. Для объекта, каждое свойство которого имеет только одно значение, App Engine сохраняет значение свойства в своем простом индексе единожды, а в пользовательском индексе столько раз, сколько существует обращений к данному свойству. Каждую из таких записей в индексах необходимо обновлять после каждого изменения значения свойства, поэтому чем больше индексов ссылаются на свойство, тем больше потребуется времени на его обновление.

Чтобы избежать длительного обновления объекта, хранилище данных ограничивает количество записей индекса, относящихся к одному объекту. Это ограничение достаточно велико, поэтому на работу большинства приложений оно не повлияет. Однако при некоторых обстоятельствах вы можете испытать действие данного ограничения. Например, ограничение на количество записей в индексе может превысить объект, имеющий очень много свойств с одним значением.

Каждое значение свойств, имеющих несколько значений, сохраняется в отдельной записи индекса. Объект, имеющий единственное свойство с очень большим количеством значений может превысить ограничение на количество записей в индексе.

Пользовательские индексы, ссылающиеся на несколько свойств с несколькими значениями, могут стать слишком большими очень быстро. Чтобы полностью записать такие свойства, таблица индекса должна содержать строку для каждой комбинации значений каждого свойства в индексе.

Например, следующий индекс (использован синтаксис datastore-indexes.xml) содержит свойства x и y объектов типа MyModel:

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes>
    <datastore-index kind="MyModel">
        <property name="x" direction="asc" />
        <property name="y" direction="asc" />
    </datastore-index>
</datastore-indexes>

В следующем фрагменте кода создается объект с двумя значениями свойства x и двумя значениями свойства y:

        MyModel m = new MyModel();

        m.setX(Arrays.asList("one", "two"));
        m.setY(Arrays.asList("three", "four"));

        pm.makePersistent(m);

Чтобы точно представить эти значения, нужно сохранить в индексе 12 значений свойств: два для встроенных индексов по свойствам x и y и по два для каждой из четырех комбинаций свойств x и y в пользовательском индексе. Если свойство имеет много значений, это значит, что в индексе необходимо хранить слишком много записей для одного объекта. Индекс, который ссылается на несколько свойств с несколькими значениями, можно назвать "разрастающимся", так как он может стать очень большим при добавлении всего нескольких значений.

Если вызов метода put() приведет к превышению ограничения на количество записей, возникнет ошибка и будет вызвано исключение. Если при построении нового индекса количество записей в нем превысит заданное ограничение для какого-либо объекта, запросы по данному индексу не будут выполняться, а сам индекс появится в консоли администрирования с состоянием "Error".

Можно избежать разрастания индексов, не выполняя запросы, для которых необходим пользовательский индекс, использующий списковое свойство. Как описано выше, в эту категорию входят запросы с несколькими порядками сортировки, а также сочетаниями фильтров равенства, неравенства и родителей.