INTEGRITY Документация

Использование индексов

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

Когда полезен индекс?

Индексы полезны в следующих случаях:

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

Создавать индекс

Чтобы создать индекс для таблицы D1, используйте CREATE INDEX команду SQL и укажите таблицу и столбец (или столбцы), по которым нужно создать индекс.

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

CREATE TABLE IF NOT EXISTS orders (
    order_id INTEGER PRIMARY KEY,
    customer_id STRING NOT NULL, -- for example, a unique ID aba0e360-1e04-41b3-91a0-1f2263e1e0fb
    order_date STRING NOT NULL,
    status INTEGER NOT NULL,
    last_updated_date STRING NOT NULL
)

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

CREATE INDEX IF NOT EXISTS idx_orders_customer_id ON orders(customer_id)

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

-- Uses the index: the indexed column is referenced by the query.
SELECT * FROM orders WHERE customer_id = ?

-- Does not use the index: customer_id is not in the query.
SELECT * FROM orders WHERE order_date = '2023-05-01'

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

Запустите PRAGMA optimize

После создания индекса выполните PRAGMA optimize команду, чтобы повысить производительность базы данных.

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

Подробнее см. в PRAGMA optimize.

Список индексов

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

SELECT name, type, sql FROM sqlite_schema WHERE type IN ('index');

Это вернёт вывод, похожий на приведённый ниже:

┌──────────────────────────────────┬───────┬────────────────────────────────────────┐
│ name                             │ type  │ sql                                    │
├──────────────────────────────────┼───────┼────────────────────────────────────────┤
│ idx_users_id                     │ index │ CREATE INDEX idx_users_id ON users(id) │
└──────────────────────────────────┴───────┴────────────────────────────────────────┘

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

Тестирование индекса

Чтобы убедиться, что для запроса использовался индекс, добавьте перед ним EXPLAIN QUERY PLAN. Это выведет план запроса для следующего оператора, включая то, какие индексы использовались (если использовались).

Например, если предположить, что users таблица имеет email_address TEXT столбец и создали индекс CREATE UNIQUE INDEX idx_email_address ON users(email_address), любой запрос с предикатом по email_address должен использовать ваш индекс.

EXPLAIN QUERY PLAN SELECT * FROM users WHERE email_address = '[email protected]';
QUERY PLAN
`--SEARCH users USING INDEX idx_email_address (email_address=?)

Просмотрите USING INDEX <INDEX_NAME> вывод планировщика запросов, подтверждающий, что индекс был использован.

Это также довольно распространённый сценарий использования индекса. Поиск пользователя по адресу электронной почты часто встречается в системах входа (аутентификации).

Индекс может уменьшить количество строк, считываемых запросом. Используйте meta объект для оценки использования. См. "Можно ли использовать индекс, чтобы уменьшить количество строк, прочитанных запросом?" и "Как оценить свой (итоговый) счёт?".

Индексы и ваш счёт

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

Неожиданно большие счета за D1 чаще всего вызваны небольшим числом часто выполняемых запросов, каждый из которых читает (или записывает) намного больше строк, чем возвращает. В следующей таблице перечислены типичные паттерны, затрагивающие слишком много строк, и способы их исправления. Используйте EXPLAIN QUERY PLAN чтобы проверить, выполняет ли запрос полное SCAN (считывает каждую строку) или SEARCH ... USING INDEX (переходит сразу к подходящим строкам).

Шаблон Почему запрос считывает или записывает слишком много строк Исправление
WHERE column = ? по неиндексированному столбцу D1 сканирует всю таблицу при каждом вызове. Создавать индекс по отфильтрованному столбцу.
WHERE a = ? AND b = ? без индекса ни по одному из столбцов D1 сканирует всю таблицу. Индекс только по одному из двух столбцов позволяет избежать полного сканирования, но D1 всё равно считывает каждую строку, соответствующую этому столбцу, до фильтрации по второму столбцу. Создайте составной индекс на (a, b) чтобы D1 мог сужать выборку сразу по обоим столбцам.
JOIN other ON other.x = main.y где other.x не индексируется В зависимости от плана запроса D1 может неоднократно сканировать соединяемую таблицу или создать временный индекс. Создайте индекс для столбца, используемого в ON условие (здесь other.x), а затем проверьте план с помощью EXPLAIN QUERY PLAN.
Коррелированный подзапрос, такой как WHERE id = (SELECT ... WHERE inner.key = outer.key ...) В зависимости от плана запроса D1 может вычислять вложенный запрос для каждой строки кандидата. Сканирование во вложенном запросе способно кратно увеличить число прочитанных строк. Создайте индексы для столбцов, по которым подзапрос выполняет фильтрацию и соединение, а затем проверьте план с помощью EXPLAIN QUERY PLAN.
ORDER BY RANDOM() LIMIT 1 D1 приходится считывать и сортировать весь результирующий набор, чтобы выбрать случайную строку: индекс здесь не поможет. Избегайте ORDER BY RANDOM() на больших таблицах. Выберите стратегию выборки, подходящую по типу первичного ключа, распределению значений и требуемой случайности.
WHERE column LIKE '%term%' (начальный подстановочный знак), в том числе внутри COUNT(*) Ведущий % не позволяет обычному B-tree индексу оптимизировать LIKE, поэтому запросу обычно требуется полное сканирование. По возможности уберите начальный подстановочный знак. Например, поиск по префиксу вида LIKE 'term%' в некоторых случаях могут использовать индекс. Для поиска произвольных подстрок рассмотрите FTS5 с токенизатором trigram, что может оптимизировать шаблоны, содержащие не менее трёх последовательных символов Unicode без подстановочных знаков. Индексы FTS5 увеличивают затраты на хранение и запись, поэтому оцените их влияние на вашей нагрузке. Проверьте оба варианта с помощью EXPLAIN QUERY PLAN.
Повторный запуск CREATE INDEX (или другие изменения схемы) при каждом запросе Создание индекса записывает строку для каждой индексируемой строки, а запись тарифицируется по более высокой ставке, чем чтение. Если делать это при каждом запросе, эти затраты повторяются. Выполните изменения схемы один раз с помощью Миграции D1, а не на горячем пути вашего приложения.

Составные индексы

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

При индексе CREATE INDEX idx_customer_id_transaction_date ON transactions(customer_id, transaction_date), в следующей таблице показано, когда индекс используется, а когда нет:

Запрос Индекс использован?
SELECT * FROM transactions WHERE customer_id = '1234' AND transaction_date = '2023-03-25' Да: указывает оба столбца индекса.
SELECT * FROM transactions WHERE transaction_date = '2023-03-28' Нет: указывает только transaction_date, и не включает другие крайние левые столбцы индекса.
SELECT * FROM transactions WHERE customer_id = '56789' Да: указывает customer_id, который является крайним левым столбцом в индексе.

Примечания:

Частичные индексы

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

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

CREATE INDEX idx_order_status_not_complete ON orders(order_status) WHERE order_status != 6

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

Удаление индексов

Используйте DROP INDEX чтобы удалить индекс. Удалённые индексы восстановить нельзя.

Особенности

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