RussianLDP Рейтинг@Mail.ru
WebMoney: 
WMZ Z294115950220 
WMR R409981405661 
WME E134003968233 
Visa 
4274 3200 2453 6495 

Small. Fast. Reliable.
Choose any three.

Обзор

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

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

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

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

Дополнительная информация предоставляется здесь и здесь.

1. Поиск

1.1. Таблицы без индексов

Большинство таблиц в SQLite состоит из ноля или большего количества строк с уникальным ключом целого числа (rowid или INTEGER PRIMARY KEY), сопровождаемый содержанием. Исключение: таблицы WITHOUT ROWID. Строки логически сохранены в порядке увеличения rowid. Как пример, эта статья использует таблицу "FruitsForSale", которая связывает различные фрукты со статусом, где они выращены и их ценой за единицу товара на рынке. Схема:

CREATE TABLE FruitsForSale(Fruit TEXT, State TEXT, Price REAL);

С некоторыми (произвольными) данными такая таблица могла бы быть логически сохранена на диске как показано на рисунке 1:

figure 1
Рис. 1: Логическое расположение таблицы "FruitsForSale"

В этом примере rowid не последовательны, но их упорядочивают. SQLite обычно создает начальный rowid и увеличивает на один с каждой добавленной строкой. Но если строки удалены, промежутки могут появиться в последовательности. И применение может управлять назначенным rowid при желании так, чтобы строки были не обязательно вставлены в основании. Но независимо от того, что происходит, rowid всегда уникальны и идут в строгом порядке по возрастанию.

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

SELECT price FROM fruitsforsale WHERE fruit='Peach';

Чтобы удовлетворить этот запрос, SQLite читает каждую строку из таблицы, проверяет, чтобы видеть, имеет ли колонка "fruit" значение "Peach" и если это так, производит столбец "price" из этой строки. Процесс иллюстрирован рис. 2. Этот алгоритм, назван полным сканированием таблицы, так как все содержание таблицы должно быть прочитано и исследовано, чтобы найти одну строку. С таблицей из 7 строк полное сканирование таблицы приемлемо, но если бы таблица содержала 7 миллионов строк, полное сканирование таблицы могло бы прочитать мегабайты содержания, чтобы найти единственное 8-байтовое число. По этой причине обычно пытаются избежать полного сканирования таблицы.

figure 2
Рис. 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 или приблизительно в миллион раз быстрее.

figure 3
Рис. 3: Поиск Rowid

1.3. Поиск индексом

Проблема с поиском информации по rowid состоит в том, что вы, вероятно, не заботитесь о том, что такое цена на "item 4", вы хотите знать цену персиков. И таким образом, поиск по rowid не полезен.

Чтобы сделать исходный запрос более эффективным, мы можем добавить индекс на колонке "fruit" таблицы "fruitsforsale":

CREATE INDEX Idx1 ON fruitsforsale(fruit);

Индекс это другая таблица, подобная оригинальной "fruitsforsale", но с содержанием (колонка fruit в этом случае), сохраненным перед rowid и со всеми строками в порядке контента. Рисунок 4 высказывает логическое мнение индекса Idx1. Колонка "fruit" это первичный ключ, используемый, чтобы упорядочить элементы таблицы, "rowid" это вторичный ключ, используемый, чтобы сломать связь, когда у двух или больше строк есть те же самые "fruit". В примере rowid должен использоваться в качестве дополнительного параметра для строк "Orange". Заметьте, что, так как rowid всегда уникален по всем элементам оригинальной таблицы, составной ключ "fruit", сопровождаемый "rowid", будет уникален по всем элементам индекса.

figure 4
Рис. 4: индекс на столбце Fruit

Этот новый индекс может использоваться, чтобы осуществить более быстрый алгоритм первоначального запроса "Price of Peaches".

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.

figure 5
Рис. 5: индексируемый поиск цены персиков

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

1.4. Многократные строки результата

В предыдущем запросе ограничение fruit='Peach' сузило результат к единственной стрке. Но та же самая техника работает, даже если получены многократные строки. Предположим, что мы искали цену апельсинов вместо персиков:

SELECT price FROM fruitsforsale WHERE fruit='Orange'

figure 6
Рис. 6: индексируемый поиск цены апельсинов

В этом случае SQLite все еще делает единственный двоичный поиск, чтобы найти первый вход индекса, где fruit='Orange'. Тогда это извлекает rowid из индекса, использует для поиска оригинальной записи таблицы через двоичный поиск и производит цену от оригинальной таблицы. Но вместо ухода ядро базы данных тогда продвигается к следующей строке индекса, чтобы повторить процесс для следующей записи fruit='Orange'. Продвижение к следующей строке индекса (или таблицы) намного менее дорогостоящее, чем выполнение двоичного поиска, так как следующая строка часто располагается на той же самой странице базы данных как текущая строка. На самом деле стоимость продвижения к следующей строке столь дешевая по сравнению с двоичным поиском, что мы обычно игнорируем ее. Таким образом, наша оценка для общей стоимости этого запроса: 3 двоичных поиска. Если количество строк вывода K, и количество строк в таблице N, то в целом стоимость выполнения запроса пропорциональна (K+1)*logN.

1.5. Многочисленные связанные AND термины WHERE

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

SELECT price FROM fruitsforsale WHERE fruit='Orange' AND state='CA'

figure 7
Рис. 7: индексируемый поиск калифорнийских апельсинов

Один подход к этому запросу должен использовать термин fruit='Orange' в WHERE, чтобы найти все строки, имеющие дело с апельсинами, затем отфильтровать те строки, отклонив любые, которые имеют states не California. Этот процесс показывает рис. 7. Это совершенно разумный подход в большинстве случаев. Да, ядро базы данных действительно должно было сделать дополнительный двоичный поиск строки с апельсинами из Флориды, который был позже отклонен, таким образом, это не было столь эффективно, как мы могли бы надеяться, хотя для многих приложений это достаточно эффективно.

Предположим, что в дополнение к индексу на "fruit" был также индекс на "state".

CREATE INDEX Idx2 ON fruitsforsale(state);

figure 8
Рис. 8: индекс на столбце State

Индекс "state" действует точно так же, как индекс "fruit", в котором это новая таблица с дополнительным столбцом перед rowid и отсортированная тем дополнительным столбцом как первичным ключом. Единственная разница в том, что в Idx2 первая колонка "state" вместо "fruit", как в Idx1. В нашем наборе данных в качестве примера в "state" есть больше избыточности и таким образом больше двойных записей. Связи все еще решены, используя rowid.

Используя новый индекс Idx2 на "state", у SQLite есть другая возможность для поиска цены калифорнийских апельсинов: это может искать каждую строку, которая содержит фрукты из Калифорнии, и отфильтровать те строки, которые не являются апельсинами.

figure 9
Рис. 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);

figure 10
Рис. 10: два индекса столбца

Многостолбцовый индекс следует за тем же самым образцом, как индекс отдельного столбца: индексированные столбцы добавляются перед rowid. Единственная разница в том, что теперь многочисленные колонки добавляются. Крайний левый столбец это первичный ключ, используемый для порядка строк в индексе. Вторая колонка используется, чтобы связывать крайний левый столбец. Если бы была третья колонка, она использовалась бы, чтобы сломать связи для первых двух колонок. И т. д. для всех колонок в индексе. Поскольку rowid, как гарантируют, будет уникален, каждая строка индекса будет уникальна, даже если все колонки содержания для двух строк будут тем же самым. Такого случая не происходит в наших типовых данных, но есть один случай (fruit='Orange'), где есть связь на первой колонке, которая должна быть сломана второй колонкой.

Учитывая новый многостолбцовый индекс Idx3, для SQLite теперь возможно найти цену калифорнийских апельсинов, используя только 2 двоичных поиска:

SELECT price FROM fruitsforsale WHERE fruit='Orange' AND state='CA'

figure 11
Рис. 11: поиск, используя два индекса столбца

С индексом Idx3 на обеих колонках, которые ограничиваются оператором Where, SQLite может сделать единственный двоичный поиск против Idx3, чтобы найти один rowid для калифорнийских апельсинов, затем сделать единственный двоичный поиск, чтобы найти цену за тот пункт в оригинальной таблице. Нет никаких тупиков и никаких потраченных впустую двоичных поисков. Это более эффективный запрос.

Обратите внимание на то, что Idx3 содержит ту же информацию, как оригинальный Idx1. И поэтому если у нас есть Idx3, нам действительно больше не нужен Idx1. Запрос "price of peaches" может быть удовлетворена, используя Idx3, просто игнорируя столбец "state" в Idx3:

SELECT price FROM fruitsforsale WHERE fruit='Peach'

figure 12
Рис. 12: поиск отдельного столбца на многостолбцовом индексе

Следовательно, хорошее эмпирическое правило: ваша схема базы данных никогда не должна содержать два индекса, где один индекс это префикс другого. Пропустите индекс с меньшим количеством колонок. SQLite все еще будет в состоянии сделать эффективные поиски с более длинным индексом.

1.7. Покрытие индексов

Запрос "price of California oranges" был сделан более эффективным с помощью двух индексов столбца. Но SQLite может сделать еще лучше с тремя индексами столбца, которые также включают столбец "price":

CREATE INDEX Idx4 ON FruitsForSale(fruit, state, price);

figure 13
Рис. 13: закрывающий индекс

Этот новый индекс содержит все колонки оригинальной таблицы FruitsForSale, которые используются запросом: критерии поиска и вывод. Мы называем это "покрывающим индексом". Поскольку вся необходимая информация находится в покрывающем индексе, SQLite никогда не должен консультироваться с оригинальной таблицей, чтобы найти цену.

SELECT price FROM fruitsforsale WHERE fruit='Orange' AND state='CA';

figure 14
Рис. 14: запрос, используя покрывающий индекс

Следовательно, добавляя дополнительные колонки "output" в конец индекса, можно избежать необходимости сослаться на оригинальную таблицу и таким образом сократить количество двоичных поисков запроса вдвое. Это улучшение постоянного множителя работы (примерно удвоение скорости). Но с другой стороны, это также просто обработка: двойное исполнительное увеличение не почти столь же существенное, как один миллион увеличений, замеченных, когда таблица была сначала проиндексирована. И для большинства запросов, вряд ли будет замечено различие между 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, чтобы найти конечный результат. Следующее иллюстрирует этот процесс:

figure 15
Рис. 15: запрос с ограничениями OR

Диаграмма выше подразумевает, что SQLite вычисляет все rowid сначала и затем объединяет их с операцией объединения прежде, чем начать делать rowid-поиски на оригинальной таблице. В действительности rowid поиски вкраплены rowid вычислениями. SQLite использует один индекс за один раз, чтобы найти rowid, помня, какой rowid это видело прежде, чтобы избежать дубликатов.

Для метода OR UNION, показанного выше, чтобы быть полезным, должен быть доступный индекс, который помогает решить каждый OR-термин в WHERE. Если бы даже единственный OR-термин не внесен в указатель, то полное сканирование таблицы должно было бы быть сделано, чтобы найти rowid, произведенные одним термином, и если SQLite должен сделать полное сканирование таблицы, он мог бы также сделать это на оригинальной таблице и получить все результаты в единственном проходе, не имея необходимость связываться с операциями объединения и последующими двоичными поисками.

Каждый видит, как метод OR UNION мог также быть усилен, чтобы использовать многократные индексы на запросах, где у оператора Where есть условия, связанные AND, при помощи оператора пересечения вместо союза. Много систем базы данных SQL сделают просто это. Но увеличение производительности от использования единственного индекса небольшое и таким образом, SQLite не осуществляет эту технику сейчас. Однако, будущая версия SQLite могла бы быть увеличена, чтобы поддержать AND-by-INTERSECT.

2. Сортировка

SQLite (как все другие движки базы данных SQL) может также использовать индексы, чтобы удовлетворить пункты ORDER BY в запросе, в дополнение к ускорению поиска. Другими словами, индексы могут использоваться, чтобы ускорить сортировку, а также поиск.

Когда никакие соответствующие индексы не доступны, запрос с пунктом ORDER BY должен быть сортирован как отдельный шаг. Рассмотрите этот запрос:

SELECT * FROM fruitsforsale ORDER BY fruit;

SQLite обрабатывает это, собирая весь вывод запроса и затем выполняя вывод через сортировщика.

figure 16
Рис. 16: сортировка без индекса

Если количество строк вывода K, то время сортировки пропорционально KlogK. Если K маленький, время сортировки обычно не фактор, но в большом запросе, как вышеупомянутый, где K = N, время сортировки может быть намного больше, чем время, чтобы сделать полное сканирование таблицы. Кроме того, вывод накоплен во временном хранении (который мог бы быть в оперативной памяти или на диске, в зависимости от различного времени компиляции и параметров настройки во время выполнения), это может означать, что много временного хранения требуется, чтобы заканчивать запрос.

2.1. Сортировка по Rowid

Поскольку сортировка может быть дорогой, SQLite упорно работает, чтобы игнорировать пункты ORDER BY. Если SQLite решит, что вывод естественно появится в определенном порядке, то никакая сортировка не сделана. Так, например, если вы будете просить вывод в порядке rowid, никакая сортировка не будет сделана:

SELECT * FROM fruitsforsale ORDER BY rowid;

figure 17
Рис. 17: Сортировка по Rowid

Можно также просить сортировку обратного порядка:

SELECT * FROM fruitsforsale ORDER BY rowid DESC;

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

2.2. Сортировка индексом

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

Если индекс доступен на колонке ORDER BY, тот индекс может использоваться для сортировки. Считайте запрос обо всех пунктах сортированным по "fruit":

SELECT * FROM fruitsforsale ORDER BY fruit;

figure 18
Рис. 18: сортировка с индексом

Индекс Idx1 просматривается сверху донизу (или от основания до вершины, если использовано "ORDER BY fruit DESC"), чтобы найти rowid для каждого пункта в порядке fruit. Тогда для каждого rowid, двоичный поиск сделан и выведена нужная строка. Таким образом вывод появляется в требуемом порядке без потребности собрать весь вывод и сортировать его, используя отдельный шаг.

Но это действительно экономит время? Количество шагов в оригинальной сортировке пропорционально NlogN, именно так много времени это берет для сортировки N строк. Но когда мы используем Idx1 как показано здесь, мы должны сделать поисков rowid, которые занимают logN время каждый, таким образом, полное время NlogN, то же самое!

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

2.3. Сортировка, покрывая индекс

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

figure 19
Рис. 19: сортировка с закрывающим индексом

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

3. Поиск и сортировка в то же время

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

3.1. Поиск и сортировка с многостолбцовым индексом

Предположим, что мы хотим найти цены всех видов апельсинов, отсортированные в порядке места, где они выращены. Запрос:

SELECT price FROM fruitforsale WHERE fruit='Orange' ORDER BY state

Запрос содержит ограничение поиска в операторе Where и порядок сортировки в пункте ORDER BY. Поиск и сортировка могут быть достигнуты в то же время, используя Idx3 с двумя индексами столбца.

figure 20
Рис. 20: поиск и сортировка многостолбцовым индексом

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

Вы заметите, что нет никакого блока сортировки нигде на вышеупомянутой диаграмме. Пункт ORDER BY запроса исчез. Никакая сортировка не должна быть сделана здесь, потому что порядок вывода определен колонкой state, а она также, оказывается, первая колонка после фруктовой колонки в индексе. Так, если мы просмотрим записи индекса, у которых есть та же самая стоимость для фруктовой колонки сверху донизу, те элементы индекса, как гарантируют, будут упорядочены колонкой state.

3.2. Поиск и сортировка с закрывающим индексом

Закрывающий индекс может также использоваться, чтобы искать и сортировать в то же время. Рассмотрите следующее:

SELECT * FROM fruitforsale WHERE fruit='Orange' ORDER BY state

figure 21
Рис. 21: поиск и сортировка, покрывая индекс

Как прежде, SQLite делает единственный двоичный поиск диапазона строк в покрывающем индексе, которые удовлетворяют оператор Where, просмотры, которые располагаются сверху донизу, чтобы получить желаемые результаты. Строки, которые удовлетворяют оператор Where, как гарантируют, будут смежными, так как оператор WHERE это ограничение равенства на крайний левый столбец индекса. Просматривая соответствующие строки индекса сверху донизу, вывод, как гарантируют, будет упорядочен state, так как колонка state следующая колонка направо от fruit. И таким образом, получающийся запрос очень эффективен.

SQLite может потянуть подобную уловку для сортировки по убыванию ORDER BY:

SELECT * FROM fruitforsale WHERE fruit='Orange' ORDER BY state DESC

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

3.3. Частичная сортировка, используя индекс (иначе блочная сортировка)

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

SELECT * FROM fruitforsale ORDER BY fruit, price

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

figure 22
Рис. 22: частичная сортировка индексом

В примере, вместо единственноой сортировки 7 элементов, есть 5 сортировок одного элемента каждая и 1 сортировка 2 элементов для fruit='Orange'.

Преимущества выполнения многих меньших сортировок вместо единственной общей:

  1. Многократные маленькие сортировки коллективно используют меньше циклов CPU, чем единственная большая.
  2. Каждой маленькой сортировкой управляют независимо, так намного меньше информации должно быть сохранено во временном хранении в любой момент.
  3. Те колонки ORDER BY, которые происходят уже в правильном порядке из-за индексов, могут быть опущены от ключа сортировки, далее уменьшая требования хранения и процессорное время.
  4. Строки вывода могут быть возвращены к приложению, поскольку каждая маленькая сортировка заканчивается, и задолго до того, как сканирование таблицы завершено.
  5. Если есть LIMIT, могло бы быть возможно избежать просматривать всю таблицу.

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

4. Таблицы WITHOUT ROWID

Основные принципы, описанные выше, обращаются к обычным таблицам rowid и к таблицам WITHOUT ROWID. Единственная разница в том, что колонка rowid, которая служит ключом для таблиц и появляется как самый правый термин в индексах заменяется PRIMARY KEY.