Вторичные индексы

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

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

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

В транзакционных системах использование индексов позволяет сократить или исключить деградацию производительности и повышение стоимости выполнения запросов при росте объема хранимых данных.

В данной статье описаны основные операции по работе со вторичными индексами и даны ссылки на подробные материалы по каждой операции. Информация о разных типах вторичных индексов и особенностях их устройства находится в статье Вторичные индексы раздела "Концепции".

Создание вторичных индексов

Вторичный индекс является объектом схемы данных и может быть определен при создании таблицы командой YQL CREATE TABLE, или добавлен к ней позднее командой YQL ALTER TABLE.

Команда создания индекса table index add поддерживается в YDB CLI.

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

Использование индекса возможно только в порядке включенных в него полей. Если в индексе два поля a и b, то такой индекс может быть эффективно использован для запросов вида:

  • WHERE a = $var1 AND b = $var2;
  • WHERE a = $var1;
  • WHERE a > $var1, а также другие операторы сравнения;
  • WHERE a = $var1 AND b > $var2, а также любые другие операторы сравнения, но первое поле должно проверяться на равенство.

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

  • WHERE b = $var1;
  • WHERE a > $var1 AND b > $var2, точнее эта запись будет равнозначна WHERE a > $var1 с точки зрения применения индекса;
  • WHERE b > $var1.

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

Применение вторичных индексов при выборке данных

Для обращения к таблице по вторичному индексу его имя должно быть явно указано в секции VIEW после имени таблицы, как описано в статье про команду SELECT YQL. Например, для получения из таблицы Заказов (orders) выборки заказов клиента с заданным ID (id_customer) запрос будет выглядеть следующим образом:

DECLARE $customer_id AS Uint64;
SELECT *
FROM   orders VIEW idx_customer AS o
WHERE  o.id_customer = $customer_id

, где idx_customer — имя вторичного индекса на таблице orders, первым в котором указано поле id_customer.

Без указания секции VIEW для выполнения такого запроса будет полностью просканирована таблица orders.

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

Проверка стоимости запроса

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

При использовании YDB CLI вывод статистики после исполнения команды yql включается опцией --stats. Все YDB SDK также содержат структуры, содержащие статистику после исполнения запросов. При исполнении запросов в UI рядом с закладкой результатов также присутствует закладка со статистикой.

Обновление данных с использованием вторичного индекса

Команды YQL изменения записей (UPDATE, UPSERT, REPLACE) не позволяют указать на использование вторичного индекса для поиска данных, поэтому попытка выполнить UPDATE ... WHERE indexed_field = $value приведет к полному сканированию таблицы. Чтобы избежать этого, можно предварительно выполнить SELECT по индексу с получением значения первичного ключа, а затем выполнить UPDATE по первичному ключу. Также можно воспользоваться инструкцией UPDATE ON.

Чтобы обновить данные в таблице table1, выполните запрос:

$to_update = (
    SELECT pk_field, $f1 AS field1, $f2 AS field2, ...
    FROM   table1 VIEW idx_field3
    WHERE  field3 = $f3)

UPDATE table1 ON SELECT * FROM $to_update

Примечание

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

Удаление данных с использованием вторичного индекса

Для удаления данных по вторичному индексу используется SELECT c предикатом по вторичному индексу, а затем вызывается инструкция DELETE ON.

Чтобы удалить все данные о сериалах с нулевым количеством просмотров в таблице series, выполните запрос:

DELETE FROM series ON
SELECT series_id
FROM series VIEW views_index
WHERE views = 0;

Примечание

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

Атомарная замена вторичного индекса

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

Атомарно заменить существующий индекс можно с помощью команды YDB CLI ydb table index rename с параметром --replace.

Производительность записи в таблицы со вторичными индексами

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

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

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