Использование вторичных индексов в RethinkDB
Вторичные индексы — это структуры данных, которые повышают скорость многих запросов на чтение за счёт незначительного увеличения объёма хранилища и снижения производительности операций записи.
RethinkDB поддерживает различные типы вторичных индексов:
- Простые индексы, основанные на значении одного поля.
- Составные индексы, основанные на нескольких полях.
- Многоуровневые индексы, основанные на массивах значений.
- Индексы, основанные на произвольных выражениях.
Простые индексы
Используйте простые индексы для эффективного извлечения и упорядочивания документов по значению одного поля.
Создание
# Create a secondary index on the last_name attribute
r.table("users").index_create("last_name").run(conn)
# Wait for the index to be ready to use
r.table("users").index_wait("last_name").run(conn)
Запросы
# Get all users whose last name is "Smith"
r.table("users").get_all("Smith", :index => "last_name").run(conn)
# Get all users whose last names are "Smith" or "Lewis"
r.table("users").get_all("Smith", "Lewis", :index => "last_name").run(conn)
# Get all users whose last names are between "Smith" and "Wade"
r.table("users").between("Smith", "Wade", :index => "last_name").run(conn)
# Efficiently order users by last name using an index
r.table("users").order_by(:index => "last_name").run(conn)
# For each blog post, return the post and its author using the last_name index
r.table("posts").eq_join("author_last_name", r.table("users"), :index => "last_name") \
.zip().run(conn)
Хотите узнать больше о соединениях в RethinkDB? См. как использовать соединения для запросов с отношениями один ко многим и многие ко многим.
Составные индексы
Составные индексы используют массивы для эффективного извлечения документов по нескольким полям.
Создание
# Create a compound secondary index based on the first_name and last_name attributes
r.table("users").index_create("full_name"){ |row|
[row["last_name"], row["first_name"]]
}.run(conn)
# Wait for the index to be ready to use
r.table("users").index_wait("full_name").run(conn)
Запросы
# Get all users whose full name is John Smith.
r.table("users").get_all(["Smith", "John"], :index => "full_name").run(conn)
# Get all users whose full name is between "John Smith" and "Wade Welles"
r.table("users").between(
["Smith", "John"], ["Welles", "Wade"], :index => "full_name"
).run(conn)
# Get all users whose last name is Smith.
r.table("users").between(
["Smith", r.minval], ["Smith", r.maxval], :index => "full_name"
).run(conn)
# Efficiently order users by first name and last name using an index
r.table("users").order_by(:index => "full_name").run(conn)
# For each blog post, return the post and its author using the full_name index
# (assume "author_full_name" is the name of a field in "posts")
r.table("posts").eq_join("author_full_name", r.table("users"), :index => "full_name") \
.run(conn)
Внутри RethinkDB составные и простые индексы представляют собой один тип индекса; составные индексы — это просто частный случай обычного индекса, который возвращает массив, а не одно значение.
Многоуровневые индексы
При использовании простых и составных индексов документ индексируется не более чем по одному ключу индекса: одно значение для простого индекса и набор значений для составного индекса. Несколько документов могут иметь один и тот же ключ индекса. С многоуровневым индексом документ может быть индексирован по нескольким ключам в одном индексе. Например, в блоге пост может иметь несколько тегов, и каждый тег может относиться к нескольким постам.
Ключи в многоуровневом индексе могут быть одиночными значениями, составными значениями или даже произвольными выражениями. (См. раздел ниже для получения дополнительной информации об индексах, использующих функции.)
Создание
Предположим, каждый пост имеет поле tags , отображающее массив тегов. Схема таблицы posts будет примерно такой:
{
:title => "...",
:content => "...",
:tags => [ <tag1>, <tag2>, ... ]
}
# Create the multi index based on the field tags
r.table("posts").index_create("tags", :multi => true)
# Wait for the index to be ready to use
r.table("posts").index_wait("tags").run(conn)
Запросы
# Get all posts with the tag "travel" (where the field tags contains "travel")
r.table("posts").get_all("travel", :index => "tags").run(conn)
# For each tag, return the tag and the posts that have such tag
r.table("tags").eq_join("tag", r.table("posts"), :index => "tags").run(conn)
Обратите внимание, что запросы с getAll или between могут возвращать один и тот же документ несколько раз, если вы не используете команду distinct.
Индексы на произвольных выражениях ReQL
Вы можете создать индекс на произвольном выражении, передав анонимную функцию в index_create.
# A different way to do a compound index
r.table("users").index_create("full_name2"){ |user|
r.add(user["last_name"], "_", user["first_name"])
}.run(conn)
Функция, которую вы передаёте в index_create , должна быть детерминированной. На практике это означает, что вы не можете использовать функцию, содержащую подзапрос или команду r.js.
Если функция, переданная в
index_create, возвращает ошибку для заданного документа, этот документ не будет проиндексирован. Никаких ошибок для таких документов не будет.
Использование многоуровневых индексов и произвольных выражений вместе
Аналогичным образом вы можете создать многоуровневый индекс на произвольном выражении, передав параметр multi в качестве последнего параметра в indexCreate.
# Create a multi index on a ReQL expression
r.table("users").index_create("activities", :multi => true){ |activity|
activity["hobbies"] + activity["sports"]
}.run(conn)
Использование многоуровневого индекса и отображающей функции для ускорения get_all/contains
Если ваша программа часто выполняет get_all, за которым следует contains, эту операцию можно сделать более эффективной путём создания составного многоуровневого индекса, используя отображающую функцию для поля, содержащего список.
# Create the index
r.table("users").index_create("user_equipment", {:multi => true}) { |user|
user['equipment'].map { |equipment| [user['id'], equipment] }
}.run(conn)
# Query equivalent to:
# r.table("users").get_all(1).filter { |user|
# user['equipment'].contains('tent')
# }.run(conn)
r.table("users").get_all([1, "tent"], {:index =>"user_equipment"}).distinct().run(conn)
Административные операции
# list indexes on table "users"
r.table("users").index_list().run(conn)
# drop index "last_name" on table "users"
r.table("users").index_drop("last_name").run(conn)
# return the status of all indexes
r.table("users").index_status().run(conn)
# return the status of the index "last_name"
r.table("users").index_status("last_name").run(conn)
# return only when the index "last_name" is ready
r.table("users").index_wait("last_name").run(conn)
Управление индексами с помощью веб-интерфейса
Веб-интерфейс поддерживает создание и удаление простых вторичных индексов. В списке таблиц нажмите на таблицу users. Вы можете управлять индексами через панель вторичных индексов в представлении таблицы.
Примечания
Основной индекс таблицы может использоваться в любом запросе ReQL, который использует вторичный индекс.
Индексы (как вторичные, так и первичные) гарантированно обновляются успешными операциями записи. Если операция insert, update или delete выполнена успешно, изменения будут правильно отражены в индексе. (Узнайте о гарантиях согласованности RethinkDB для операций записи.)
Часть ключа вторичного индекса, используемая для быстрого поиска, зависит от длины первичного ключа (который должен быть не более 127 байт). Длина этой части составляет 238−PK, где PK — длина первичного ключа; например, если длина первичного ключа — GUID из 36 символов, это означает, что 202 символа в вторичном индексе будут значимыми. Если таблица содержит несколько записей, где первые 238−PK символов идентичны, производительность поиска резко снизится, так как RethinkDB придётся выполнить линейный поиск для нахождения правильных записей.
Вторичные индексы не будут хранить значения или объекты nil. Таким образом, результаты команды, такой как:
r.table("users").index_create("group").run(conn)
r.table("users").order_by(:index => "group").run(conn)
могут отличаться от эквивалентной команды без индекса:
r.table("users").order_by("group").run(conn)
если поле, которое индексируется, имеет неиндексируемые значения.
Это ограничение будет устранено в будущих версиях RethinkDB. Следите за прогрессом в GitHub-вопросе #1032.
В RethinkDB в настоящее время нет оптимизатора. Например, следующий запрос не будет автоматически использовать индекс:
# This query does not use a secondary index! Use get_all instead.
r.table("users").filter({:last_name => "Smith" }).run(conn)
Вам необходимо явно использовать команду get_all для использования вторичных индексов.
# This query uses a secondary index.
r.table("users").get_all("Smith", :index => "last_name").run(conn)
Вы не можете объединять несколько команд get_all. Используйте составной индекс для эффективного извлечения документов по нескольким полям.
Команда order_by , использующая вторичный индекс, не может быть последовательно выполнена после get_all. Вы можете её выполнить только после команды table. Однако вы можете последовательно выполнить order_by после команды between , если она использует тот же индекс.
RethinkDB не поддерживает уникальные вторичные индексы даже для нефрагментированных таблиц.
Подробнее
Просмотрите справку API, чтобы узнать больше о командах вторичного индекса:
- Управление индексами: index_create, index_drop и index_list
- Использование индексов: get_all, between, eq_join и order_by
© RethinkDB contributors
Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
https://rethinkdb.com/docs/secondary-indexes/ruby/