Планирование запросов
Содержание
Обзор
Лучшей особенностью SQL (во всех его реализациях, а не только в SQLite) является то, что это декларативный язык, а не процедурный. При программировании на SQL вы указываете системе, что вы хотите вычислить, а не как это вычислить. Задача определения как делегируется подсистеме планировщика запросов внутри движка базы данных SQL.
Для любого данного оператора SQL может существовать сотни, тысячи или даже миллионы различных алгоритмов выполнения операции. Все эти алгоритмы дадут правильный результат, хотя некоторые будут выполняться быстрее, чем другие. Планировщик запросов — это ИИ, который пытается выбрать самый быстрый и эффективный алгоритм для каждого оператора SQL.
Большую часть времени планировщик запросов в SQLite работает хорошо. Однако планировщику запросов нужны индексы. Эти индексы обычно должны быть добавлены программистами. В редких случаях планировщик запросов выберет не оптимальный алгоритм. В этих случаях программисты могут предоставить дополнительные подсказки, чтобы помочь планировщику запросов выполнить свою работу лучше.
Этот документ предоставляет справочную информацию о том, как работают планировщик запросов и движок запросов SQLite. Программисты могут использовать эту информацию, чтобы создавать лучшие индексы и предоставлять подсказки планировщику запросов, когда это необходимо.
Дополнительную информацию можно найти в документации планировщик запросов SQLite и планировщик запросов следующего поколения.
1. Поиск
1.1. Таблицы без индексов
Большинство таблиц в SQLite состоят из нуля или более строк с уникальным целочисленным ключом (rowid или INTEGER PRIMARY KEY) и содержимым. (Исключение составляют таблицы WITHOUT ROWID.) Строки логически хранятся в порядке возрастания rowid. В качестве примера в этой статье используется таблица под названием "FruitsForSale", которая связывает различные фрукты с государством, где они выращиваются, и их ценой за единицу на рынке. Схема такова:
CREATE TABLE FruitsForSale( Fruit TEXT, State TEXT, Price REAL );
С некоторыми (произвольными) данными такая таблица может быть логически хранима на диске, как показано на рисунке 1:
Рисунок 1: Логическая структура таблицы "FruitsForSale"
В этом примере rowid не являются последовательными, но они упорядочены. SQLite обычно создаёт rowid, начиная с единицы и увеличивая на единицу при каждом добавлении строки. Но если строки удаляются, в последовательности могут появиться пробелы. Приложение может контролировать присваиваемый rowid, если это необходимо, так что строки не обязательно вставляются в низ. Но независимо от того, что происходит, rowid всегда уникальны и строго возрастают.
Предположим, вы хотите узнать цену груш. Запрос будет следующим:
SELECT price FROM fruitsforsale WHERE fruit='Peach';
Для удовлетворения этого запроса SQLite считывает каждую строку из таблицы, проверяет, содержит ли столбец "fruit" значение "Peach", и если да, то выводит столбец "price" из этой строки. Процесс иллюстрируется на рисунке 2 ниже. Этот алгоритм называется полным сканированием таблицы, так как для поиска одной интересующей строки необходимо прочитать и проверить всё содержимое таблицы. С таблицей всего из 7 строк полное сканирование таблицы приемлемо, но если таблица содержала 7 миллионов строк, полное сканирование таблицы могло бы прочитать мегабайты данных для поиска одного 8-байтового числа. По этой причине обычно стараются избегать полного сканирования таблицы.
Рисунок 2: Полное сканирование таблицы
1.2. Поиск по rowid
Один из способов избежать полного сканирования таблицы — выполнять поиск по rowid (или эквивалентному INTEGER PRIMARY KEY). Для поиска цены груш можно выполнить запрос для записи с rowid 4:
SELECT price FROM fruitsforsale WHERE rowid=4;
Так как информация хранится в таблице в порядке rowid, SQLite может найти правильную строку с помощью бинарного поиска. Если таблица содержит N элементов, время, необходимое для поиска нужной строки, пропорционально logN, а не N, как при полном сканировании таблицы. Если таблица содержит 10 миллионов элементов, это означает, что запрос будет порядка N/logN или примерно в 1 миллион раз быстрее.
Рисунок 3: Поиск по rowid
1.3. Поиск по индексу
Проблема поиска информации по rowid заключается в том, что вам, вероятно, не нужно знать, какова цена "элемента 4" — вам нужно знать цену груш. Поэтому поиск по rowid не поможет.
Чтобы сделать исходный запрос более эффективным, мы можем добавить индекс к столбцу "fruit" таблицы "fruitsforsale" так:
CREATE INDEX Idx1 ON fruitsforsale(fruit);
Индекс — это другая таблица, похожая на исходную таблицу "fruitsforsale", но с содержимым (в данном случае столбцом fruit) хранящимся перед rowid и со всеми строками в порядке содержимого. Рисунок 4 дает логическое представление индекса Idx1. Столбец "fruit" является первичным ключом, используемым для упорядочения элементов таблицы, а "rowid" — вторичным ключом, используемым для разрешения конфликтов, когда две или более строки имеют одинаковое значение "fruit". В примере rowid используется в качестве разделителя для строк "Orange". Обратите внимание, что поскольку rowid всегда уникален для всех элементов исходной таблицы, составной ключ "fruit" и "rowid" будет уникальным для всех элементов индекса.
Рисунок 4: Индекс по столбцу Fruit
Этот новый индекс можно использовать для реализации более быстрого алгоритма для исходного запроса "Цена груш".
SELECT price FROM fruitsforsale WHERE fruit='Peach';
Запрос начинается с бинарного поиска в индексе Idx1 для записей, у которых fruit='Peach'. SQLite может выполнить этот бинарный поиск в индексе Idx1, но не в исходной таблице FruitsForSale, потому что строки в Idx1 упорядочены по столбцу "fruit". Найдя строку в индексе Idx1, у которой fruit='Peach', движок базы данных может извлечь rowid для этой строки. Затем движок базы данных выполняет второй бинарный поиск в исходной таблице FruitsForSale для поиска исходной строки, содержащей fruit='Peach'. Из строки в таблице FruitsForSale SQLite может затем извлечь значение столбца price. Эта процедура иллюстрируется на рисунке 5.
Рисунок 5: Поиск по индексу для цены груш
SQLite должен выполнить два бинарных поиска, чтобы найти цену груш с помощью описанного выше метода. Но для таблицы с большим количеством строк это по-прежнему намного быстрее, чем полное сканирование таблицы.
1.4. Несколько строк результатов
В предыдущем запросе ограничение fruit='Peach' сузило результат до одной строки. Но тот же метод работает даже если получается несколько строк. Предположим, что мы ищем цену апельсинов, а не груш:
SELECT price FROM fruitsforsale WHERE fruit='Orange'
Рисунок 6: Поиск по индексу для цены апельсинов
В этом случае SQLite по-прежнему выполняет один бинарный поиск, чтобы найти первую запись в индексе, где fruit='Orange'. Затем он извлекает rowid из индекса и использует этот rowid для поиска в исходной таблице через бинарный поиск и выводит price из исходной таблицы. Но вместо завершения, движок базы данных затем переходит к следующей строке индекса, чтобы повторить процесс для следующей записи fruit='Orange'. Переход к следующей строке индекса (или таблицы) намного менее затратно, чем бинарный поиск, так как следующая строка часто находится на той же странице базы данных, что и текущая строка. Фактически, стоимость перехода к следующей строке настолько низка по сравнению с бинарным поиском, что мы обычно её игнорируем. Таким образом, наша оценка общей стоимости этого запроса составляет 3 бинарных поиска. Если количество строк вывода равно K, а количество строк в таблице — N, то в общем случае стоимость выполнения запроса пропорциональна (K+1)*logN.
1.5. Несколько терминов в операторе WHERE, соединенных оператором AND
Далее, предположим, что вы хотите узнать цену не просто любого апельсина, а конкретно выращенного в Калифорнии. Соответствующим запросом будет следующий:
SELECT price FROM fruitsforsale WHERE fruit='Orange' AND state='CA'
Рисунок 7: Поиск индексированных апельсинов из Калифорнии
Один подход к этому запросу заключается в использовании условия fruit='Orange', чтобы найти все строки, относящиеся к апельсинам, а затем отфильтровать эти строки, отклоняя те, которые происходят из других штатов, кроме Калифорнии. Этот процесс показан на рисунке 7 выше. Это вполне разумный подход в большинстве случаев. Да, движку базы данных пришлось выполнить дополнительный бинарный поиск для строки с апельсином из Флориды, которая затем была отклонена, поэтому он не был таким эффективным, как мы могли бы надеяться, хотя для многих приложений он достаточно эффективен.
Предположим, что помимо индекса по столбцу "fruit" существует также индекс по столбцу "state".
CREATE INDEX Idx2 ON fruitsforsale(state);
Рисунок 8: Индекс по столбцу State
Индекс "state" работает так же, как и индекс "fruit", в том смысле, что это новая таблица с дополнительным столбцом перед rowid и сортируется по этому дополнительному столбцу в качестве первичного ключа. Единственное различие состоит в том, что в Idx2 первый столбец — "state", а не "fruit", как в Idx1. В нашем примере данных существует больше избыточности в столбце "state", поэтому там больше дублирующихся записей. Связи все равно разрешаются с помощью rowid.
Используя новый индекс Idx2 по столбцу "state", SQLite имеет еще одну возможность для поиска цены Калифорнийских апельсинов: он может искать каждую строку, содержащую фрукты из Калифорнии, и фильтровать те строки, которые не являются апельсинами.
Рисунок 9: Индексированный поиск Калифорнийских апельсинов
Использование Idx2 вместо Idx1 заставляет SQLite проверять другой набор строк, но в конечном итоге он получает тот же ответ (что очень важно — помните, что индексы никогда не должны изменять ответ, а только помогают SQLite быстрее его получить) и выполняет ту же работу. Таким образом, индекс Idx2 не помог производительности в этом случае.
В нашем примере последние два запроса занимают одинаковое время. Так какой индекс, Idx1 или Idx2, выберет SQLite? Если команда ANALYZE была запущена на базе данных, чтобы SQLite мог собрать статистику о доступных индексах, то SQLite будет знать, что индекс Idx1 обычно сужает поиск до одной записи (наш пример с fruit='Orange' — исключение из этого правила), тогда как индекс Idx2 обычно сужает поиск только до двух строк. Поэтому, при прочих равных, SQLite выберет Idx1 с надеждой сузить поиск до минимального числа строк. Этот выбор возможен только благодаря статистике, предоставленной ANALYZE. Если ANALYZE не выполнялся, то выбор индекса для использования произвольный.
1.6. Многостолбцовые индексы
Чтобы получить максимальную производительность запроса с несколькими терминами AND в операторе WHERE, вам действительно нужен многостолбцовый индекс со столбцами для каждого из терминов AND. В этом случае мы создаем новый индекс по столбцам "fruit" и "state" в таблице FruitsForSale:
CREATE INDEX Idx3 ON FruitsForSale(fruit, state);
Рисунок 1: Индекс из двух столбцов
Многостолбцовый индекс следует той же схеме, что и одностолбцовый индекс; индексированные столбцы добавляются перед rowid. Единственное различие состоит в том, что теперь добавляется несколько столбцов. Левый столбец является первичным ключом, используемым для упорядочивания строк в индексе. Второй столбец используется для разрешения совпадений в левом столбце. Если был бы третий столбец, он использовался бы для разрешения совпадений по первым двум столбцам. И так далее для всех столбцов в индексе. Поскольку rowid гарантированно уникален, каждая строка индекса будет уникальной, даже если все столбцы содержимого двух строк одинаковы. В нашем примере данных такой случай не встречается, но есть один случай (fruit='Orange'), где есть совпадение по первому столбцу, которое должно быть разрешено по второму столбцу.
Учитывая новый многостолбцовый индекс Idx3, теперь SQLite может найти цену Калифорнийских апельсинов, используя всего 2 двоичных поиска:
SELECT price FROM fruitsforsale WHERE fruit='Orange' AND state='CA'
Рисунок 11: Поиск с использованием индекса из двух столбцов
С индексом Idx3 по обоим столбцам, ограниченным оператором WHERE, SQLite может выполнить один двоичный поиск по Idx3, чтобы найти один rowid для Калифорнийских апельсинов, а затем выполнить один двоичный поиск, чтобы найти цену этого элемента в исходной таблице. Нет тупиков и не тратится время на ненужные двоичные поиски. Это более эффективный запрос.
Обратите внимание, что Idx3 содержит всю ту же информацию, что и исходный Idx1. И поэтому, если у нас есть Idx3, нам больше не нужен Idx1. Запрос "цена персиков" может быть удовлетворен с использованием Idx3, просто проигнорировав столбец "state" в Idx3:
SELECT price FROM fruitsforsale WHERE fruit='Peach'
Рисунок 12: Поиск по одному столбцу в многостолбцовом индексе
Следовательно, хорошим правилом является то, что ваша схема базы данных никогда не должна содержать два индекса, где один индекс является префиксом другого. Удалите индекс с меньшим количеством столбцов. SQLite все равно сможет выполнять эффективные поиски с более длинным индексом.
1.7. Охватывающие индексы
Запрос "цена Калифорнийских апельсинов" был сделан более эффективным за счет использования индекса из двух столбцов. Но SQLite может сделать еще лучше с индексом из трех столбцов, который также включает столбец "price":
CREATE INDEX Idx4 ON FruitsForSale(fruit, state, price);
Рисунок 13: Охватывающий индекс
Этот новый индекс содержит все столбцы исходной таблицы FruitsForSale, которые используются запросом — как термины поиска, так и выходные данные. Мы называем это "охватывающим индексом". Поскольку вся необходимая информация находится в охватывающем индексе, SQLite никогда не обращается к исходной таблице для нахождения цены.
SELECT price FROM fruitsforsale WHERE fruit='Orange' AND state='CA';
Рисунок 14: Запрос с использованием охватывающего индекса
Следовательно, добавив дополнительные столбцы "вывода" в конец индекса, можно избежать обращения к исходной таблице и, таким образом, сократить количество двоичных поисков для запроса вдвое. Это улучшение производительности на постоянный множитель (приблизительно удвоение скорости). Но с другой стороны, это всего лишь уточнение; удвоение производительности не так драматично, как миллионное увеличение, наблюдаемое при первоначальной индексации таблицы. И для большинства запросов разницу между 1 микросекундой и 2 микросекундами, скорее всего, не заметят.
1.8. Термины OR в операторе WHERE
Многостолбцовые индексы работают только в том случае, если термины ограничения в операторе WHERE запроса соединены оператором AND. Поэтому Idx3 и Idx4 полезны, когда поиск ведется по элементам, которые одновременно являются апельсинами и вырощены в Калифорнии, но ни один из индексов не будет полезен, если мы хотим все элементы, которые являются либо апельсинами, *или* вырощены в Калифорнии.
SELECT price FROM FruitsForSale WHERE fruit='Orange' OR state='CA';
При столкновении с терминами OR в операторе WHERE SQLite рассматривает каждый термин OR отдельно и пытается использовать индекс для поиска rowid, связанных с каждым термином. Затем он берет объединение полученных наборов rowid, чтобы найти конечный результат. На рисунке ниже показан этот процесс:
Рисунок 15: Запрос с ограничениями OR
Диаграмма выше подразумевает, что SQLite сначала вычисляет все rowid, а затем объединяет их с помощью операции объединения, прежде чем начинать поиск rowid в исходной таблице. На самом деле, поиск rowid чередуется с вычислением rowid. SQLite использует один индекс за раз для поиска rowid, запоминая уже встреченные rowid, чтобы избежать дубликатов. Однако это просто деталь реализации. Хотя диаграмма не на 100% точна, она дает хорошее общее представление о том, что происходит.
Для того, чтобы техника OR-by-UNION, показанная выше, была полезна, должен быть доступен индекс, который помогает разрешить каждый термин OR в операторе WHERE. Если даже один термин OR не индексирован, то для поиска rowid, созданных этим одним термином, придется сделать полный поиск по таблице, и если SQLite должен сделать полный поиск по таблице, то он может так же хорошо сделать это по исходной таблице и получить все результаты за один проход, не прибегая к операциям объединения и последующим двоичным поискам.
Можно увидеть, как техника OR-by-UNION также может быть использована для использования нескольких индексов в запросах, где оператор WHERE имеет термины, соединенные оператором AND, используя оператор пересечения вместо объединения. Многие СУБД SQL будут делать именно это. Но прирост производительности по сравнению с использованием только одного индекса незначителен, поэтому SQLite не реализует эту технику на данный момент. Однако в будущей версии SQLite может быть добавлена поддержка AND-by-INTERSECT.
2. Сортировка
SQLite (как и все другие СУБД SQL) также может использовать индексы для удовлетворения операторов ORDER BY в запросе, помимо ускорения поиска. Другими словами, индексы могут использоваться для ускорения сортировки, а также поиска.
Когда подходящие индексы недоступны, запрос с оператором ORDER BY должен быть отсортирован как отдельная задача. Рассмотрим этот запрос:
SELECT * FROM fruitsforsale ORDER BY fruit;
SQLite обрабатывает это, собирая все результаты запроса, а затем применяет сортировку к этим результатам.
Рисунок 16: Сортировка без индекса
Если количество строк вывода равно K, то время, необходимое для сортировки, пропорционально KlogK. Если K невелико, время сортировки обычно не играет большой роли, но в запросе, таком как приведенный выше, где K==N, время, необходимое для сортировки, может быть намного больше времени, необходимого для полного сканирования таблицы. Кроме того, все выходные данные накапливаются во временном хранилище (которое может находиться как в оперативной памяти, так и на диске, в зависимости от различных параметров компиляции и выполнения), что может означать, что для завершения запроса требуется много временного хранилища.
2.1. Сортировка по rowid
Поскольку сортировка может быть дорогостоящей, SQLite усердно работает над преобразованием операторов ORDER BY в пустые операции. Если SQLite определит, что выходные данные будут естественным образом отображаться в указанном порядке, то сортировка не выполняется. Например, если вы запросили вывод в порядке rowid, то сортировка не будет выполнена:
SELECT * FROM fruitsforsale ORDER BY rowid;
Рисунок 17: Сортировка по rowid
Вы также можете запросить сортировку в обратном порядке следующим образом:
SELECT * FROM fruitsforsale ORDER BY rowid DESC;
SQLite все равно опустит шаг сортировки. Но для того, чтобы вывод отображался в правильном порядке, SQLite будет выполнять сканирование таблицы, начиная с конца и двигаясь к началу, а не начиная с начала и двигаясь к концу, как показано на рисунке 17.
2.2. Сортировка по индексу
Конечно, упорядочивание вывода запроса по rowid редко бывает полезным. Обычно нужно упорядочивать вывод по какому-либо другому столбцу.
Если доступен индекс по столбцу ORDER BY, этот индекс можно использовать для сортировки. Рассмотрим запрос на получение всех элементов, отсортированных по "fruit":
SELECT * FROM fruitsforsale ORDER BY fruit;
Рисунок 18: Сортировка с индексом
Индекс Idx1 сканируется сверху вниз (или снизу вверх, если используется "ORDER BY fruit DESC") для нахождения rowid для каждого элемента в порядке по fruit. Затем для каждого rowid выполняется двоичный поиск для поиска и вывода этой строки. Таким образом, вывод появляется в запрошенном порядке без необходимости собирать весь вывод и сортировать его с помощью отдельного шага.
Но это действительно экономит время? Количество шагов в исходной сортировке без индекса (см. рисунок 16) пропорционально NlogN, так как это то время, которое требуется для сортировки N строк. Но когда мы используем Idx1, как показано здесь, нам нужно выполнить N поисков rowid, которые каждый занимают logN времени, поэтому общее время NlogN остается тем же!
SQLite использует планировщик запросов на основе стоимости. Когда есть два или более способа решения одного и того же запроса, SQLite пытается оценить общее время, необходимое для выполнения запроса с помощью каждого плана, а затем использует план с наименьшей оцененной стоимостью. Стоимость вычисляется в основном из оценочного времени, и поэтому этот случай может зависеть от размера таблицы и того, какие ограничения в условии WHERE были доступны, и так далее. Но, как правило, сортировка по индексу, вероятно, будет выбрана, если только не по другой причине, потому что ей не нужно накапливать весь результат в временном хранилище перед сортировкой и, следовательно, используется гораздо меньше временного хранилища.
2.3. Сортировка по охватывающему индексу
Если для запроса можно использовать охватывающий индекс, то можно избежать многократных поисков по rowid, и стоимость запроса резко падает.
Рисунок 19: Сортировка с помощью охватывающего индекса
С помощью охватывающего индекса SQLite может просто пройти по индексу от одного конца к другому и предоставить вывод за время, пропорциональное N, и не нужно выделять большой буфер для хранения набора результатов.
3. Поиск и сортировка одновременно
В предыдущем обсуждении поиск и сортировка рассматривались как отдельные темы. Но на практике часто бывает необходимо выполнить поиск и сортировку одновременно. К счастью, это можно сделать, используя один индекс.
3.1. Поиск и сортировка с помощью многоколоночного индекса
Предположим, мы хотим найти цены на все виды мандаринов, отсортированные по штату, где они выращиваются. Запрос выглядит так:
SELECT price FROM fruitforsale WHERE fruit='Orange' ORDER BY state
Запрос содержит как ограничение поиска в условии WHERE, так и порядок сортировки в условии ORDER BY. И поиск, и сортировка могут быть выполнены одновременно с помощью двухколоночного индекса Idx3.
Рисунок 20: Поиск и сортировка по многоколоночному индексу
Запрос выполняет двоичный поиск в индексе, чтобы найти подмножество строк, в которых fruit='Мандарин'. (Поскольку столбец fruit является левым столбцом индекса, и строки индекса отсортированы, все такие строки будут смежными.) Затем он просматривает соответствующие строки индекса сверху вниз, чтобы получить rowid для исходной таблицы, и для каждой строки rowid выполняет двоичный поиск в исходной таблице, чтобы найти цену.
Вы заметите, что в приведенной выше диаграмме нет блока "сортировка". Условие ORDER BY запроса стало бесполезным. Здесь не нужно выполнять сортировку, потому что порядок вывода определяется столбцом state, а столбец state также оказывается первым столбцом после столбца fruit в индексе. Таким образом, если мы просматриваем записи индекса, которые имеют одинаковое значение для столбца fruit сверху вниз, эти записи индекса гарантированно будут упорядочены по столбцу state.
3.2. Поиск и сортировка с помощью охватывающего индекса
Охватывающий индекс также может быть использован для поиска и сортировки одновременно. Рассмотрим следующее:
SELECT * FROM fruitforsale WHERE fruit='Orange' ORDER BY state
Рисунок 21: Поиск и сортировка по охватывающему индексу
Как и прежде, SQLite выполняет один двоичный поиск диапазона строк в охватывающем индексе, удовлетворяющих условию WHERE, и просматривает этот диапазон сверху вниз, чтобы получить нужные результаты. Строки, удовлетворяющие условию WHERE, гарантированно будут смежными, так как условие WHERE представляет собой ограничение равенства на самый левый столбец индекса. И, просматривая соответствующие строки индекса сверху вниз, результат гарантированно будет упорядочен по state, так как столбец state находится сразу справа от столбца fruit. Таким образом, полученный запрос является очень эффективным.
SQLite может применить аналогичный трюк для убывающего ORDER BY:
SELECT * FROM fruitforsale WHERE fruit='Orange' ORDER BY state DESC
Следуется та же основная алгоритм, за исключением того, что на этот раз соответствующие строки индекса просматриваются снизу вверх вместо сверху вниз, так что состояния будут появляться в убывающем порядке.
3.3. Частичная сортировка с использованием индекса (также известная как блочная сортировка)
Иногда можно удовлетворить только часть условия ORDER BY с помощью индексов. Рассмотрим, например, следующий запрос:
SELECT * FROM fruitforsale ORDER BY fruit, price
Если для просмотра используется охватывающий индекс, столбец "fruit" будет появляться естественным образом в правильном порядке, но когда есть две или более строки с одинаковым fruit, цена может быть не в порядке. В этом случае SQLite выполняет множество небольших сортировок, по одной сортировке для каждого уникального значения fruit, а не одну большую сортировку. Рисунок 22 ниже иллюстрирует эту концепцию.
Рисунок 22: Частичная сортировка по индексу
В примере вместо одной сортировки 7 элементов выполняется 5 сортировок по одному элементу и 1 сортировка по 2 элементам для случая fruit=='Мандарин'.
Преимущества выполнения многих меньших сортировок вместо одной большой сортировки:
- Множество небольших сортировок в совокупности используют меньше циклов процессора, чем одна большая сортировка.
- Каждая небольшая сортировка выполняется независимо, что означает, что в любой момент времени нужно хранить гораздо меньше информации во временном хранилище.
- Столбцы ORDER BY, которые уже упорядочены в правильном порядке из-за индексов, можно исключить из ключа сортировки, что еще больше сокращает требования к хранилищу и времени процессора.
- Строки вывода могут быть возвращены приложению по мере завершения каждой небольшой сортировки и значительно раньше завершения просмотра таблицы.
- Если присутствует условие LIMIT, возможно, удастся избежать просмотра всей таблицы.
Благодаря этим преимуществам SQLite всегда пытается выполнить частичную сортировку с помощью индекса, даже если полная сортировка по индексу невозможна.
4. Таблицы WITHOUT ROWID
Основные принципы, описанные выше, применяются как к обычным таблицам rowid, так и к таблицам WITHOUT ROWID. Единственное различие заключается в том, что столбец rowid, который служит ключом для таблиц и появляется как самый правый элемент в индексах, заменяется первичным ключом.
Эта страница была в последний раз изменена 26 октября 2022 г. в 13:30:36 UTC
SQLite is in the Public Domain.
https://sqlite.org/queryplanner.html