Spec-Zone.ru › SQLite

Формат файла базы данных

Содержание
1. Файл базы данных
1.1. Журналы горячих операций
1.2. Страницы
1.3. Заголовок базы данных
1.3.1. Магическая строка заголовка
1.3.2. Размер страницы
1.3.3. Номера версий формата файла
1.3.4. Зарезервированные байты на страницу
1.3.5. Фракции полезной нагрузки
1.3.6. Счётчик изменений файла
1.3.7. Размер базы данных в заголовке
1.3.8. Список свободных страниц
1.3.9. Куки схемы
1.3.10. Номер формата схемы
1.3.11. Рекомендованный размер кэша
1.3.12. Параметры инкрементного вакуума
1.3.13. Кодировка текста
1.3.14. Номер версии пользователя
1.3.15. Идентификатор приложения
1.3.16. Номер версии библиотеки записи и номер, для которого версия действительна
1.3.17. Зарезервированное место в заголовке для расширения
1.4. Страница байта блокировки
1.5. Список свободных страниц
1.6. Страницы B-дерева
1.7. Страницы переполнения полезной нагрузки ячейки
1.8. Страницы карты указателей или Ptrmap
2. Слой схемы
2.1. Формат записи
2.2. Порядок сортировки записей
2.3. Представление SQL таблиц
2.4. Представление таблиц WITHOUT ROWID
2.4.1. Удаление избыточных столбцов в PRIMARY KEY таблиц WITHOUT ROWID
2.5. Представление SQL индексов
2.5.1. Удаление избыточных столбцов в вторичных индексах WITHOUT ROWID
2.6. Хранение схемы SQL базы данных
2.6.1. Альтернативные имена таблицы схемы
2.6.2. Внутренние объекты схемы
2.6.3. Таблица sqlite_sequence
2.6.4. Таблица sqlite_stat1
2.6.5. Таблица sqlite_stat2
2.6.6. Таблица sqlite_stat3
2.6.7. Таблица sqlite_stat4
3. Журнал отката
4. Журнал предварительной записи
4.1. Формат файла WAL
4.2. Алгоритм проверки контрольной суммы
4.3. Алгоритм контрольной точки
4.4. Сброс WAL
4.5. Алгоритм чтения
4.6. Формат индекса WAL

В этом документе описывается и определяется формат файла базы данных на диске, используемый во всех релизах SQLite с версии 3.0.0 (18.06.2004).

1. Файл базы данных

Полное состояние базы данных SQLite обычно содержится в одном файле на диске, называемом «основным файлом базы данных».

Во время транзакции SQLite сохраняет дополнительную информацию во втором файле, называемом «журналом отката», или, если SQLite находится в режиме WAL, в файле журнала предварительной записи.

1.1. Журналы горячих операций

Если приложение или компьютер-хост терпит сбой до завершения транзакции, то журнал отката или журнал предварительной записи содержит информацию, необходимую для восстановления основного файла базы данных в согласованное состояние. Когда журнал отката или журнал предварительной записи содержат информацию, необходимую для восстановления состояния базы данных, они называются «журнал горячих операций» или «файл WAL горячих операций». Журналы горячих операций и файлы WAL горячих операций являются фактором только в сценариях восстановления от ошибок и поэтому встречаются редко, но они являются частью состояния базы данных SQLite и поэтому не могут быть проигнорированы. В этом документе определяются форматы журнала отката и файла журнала предварительной записи, но основной акцент делается на главном файле базы данных.

1.2. Страницы

Основной файл базы данных состоит из одной или нескольких страниц. Размер страницы является степенью двойки от 512 до 65536 включительно. Все страницы в одной базе данных имеют одинаковый размер. Размер страницы для файла базы данных определяется 2-байтовым целым числом, расположенным в смещении 16 байт с начала файла базы данных.

Страницы нумеруются, начиная с 1. Максимальный номер страницы — 4294967294 (232 - 2). Минимальный размер базы данных SQLite — одна страница размером 512 байт. Максимальный размер базы данных — 4294967294 страницы по 65536 байт на страницу или 281 474 976 579 584 байт (примерно 281 терабайт). Обычно SQLite достигнет ограничения максимального размера файла для базовой файловой системы или оборудования диска задолго до того, как достигнет собственного внутреннего ограничения размера.

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

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

  • Страница B-дерева
    • Внутренняя страница B-дерева таблицы
    • Листовая страница B-дерева таблицы
    • Внутренняя страница B-дерева индекса
    • Листовая страница B-дерева индекса
  • Страница свободного списка
    • Корневая страница свободного списка
    • Листовая страница свободного списка
  • Страница переполнения полезной нагрузки
  • Страница карты указателей
  • Страница байта блокировки

Все чтения и записи из основного файла базы данных начинаются с границ страницы, а все записи имеют размер целого числа страниц. Чтение также обычно является целым числом страниц, за исключением одного случая: когда база данных открывается впервые, первые 100 байт файла базы данных (заголовок файла базы данных) читаются как единица подстраницы.

1.3. Заголовок базы данных

Первые 100 байт файла базы данных составляют заголовок файла базы данных. Заголовок файла базы данных разделен на поля, как показано в таблице ниже. Все многобайтовые поля в заголовке файла базы данных хранятся с самым старшим байтом первым (big-endian).

Формат заголовка базы данных
Смещение Размер Описание
0 16 Строка заголовка: "SQLite format 3\000"
16 2 Размер страницы базы данных в байтах. Должно быть степенью двойки от 512 до 32768 включительно, или значение 1, представляющее размер страницы 65536.
18 1 Версия записи формата файла. 1 для устаревшей версии; 2 для WAL.
19 1 Версия чтения формата файла. 1 для устаревшей версии; 2 для WAL.
20 1 Байты неиспользуемого «зарезервированного» пространства в конце каждой страницы. Обычно 0.
21 1 Максимальная вложенная фракция полезной нагрузки. Должно быть 64.
22 1 Минимальная вложенная фракция полезной нагрузки. Должно быть 32.
23 1 Фракция полезной нагрузки листа. Должно быть 32.
24 4 Счётчик изменений файла.
28 4 Размер файла базы данных в страницах. «Размер базы данных в заголовке».
32 4 Номер страницы первой корневой страницы свободного списка.
36 4 Общее количество страниц свободного списка.
40 4 Куки схемы.
44 4 Номер формата схемы. Поддерживаются форматы схем 1, 2, 3 и 4.
48 4 Размер кэша страниц по умолчанию.
52 4 Номер страницы самого большого корневого B-дерева при использовании режима автоматического вакуума или инкрементного вакуума, или ноль в противном случае.
56 4 Кодировка текста базы данных. Значение 1 означает UTF-8. Значение 2 означает UTF-16le. Значение 3 означает UTF-16be.
60 4 «Версия пользователя», считываемая и устанавливаемая с помощью команды pragma user_version.
64 4 Истина (ненулевое значение) для режима инкрементного вакуума. Ложь (ноль) в противном случае.
68 4 «Идентификатор приложения», установленный с помощью команды PRAGMA application_id.
72 20 Зарезервировано для расширения. Должно быть нулём.
92 4 Номер версии, для которого версия действительна.
96 4 SQLITE_VERSION_NUMBER

1.3.1. Магическая строка заголовка

Каждый допустимый файл базы данных SQLite начинается с следующих 16 байтов (в шестнадцатеричном формате): 53 51 4c 69 74 65 20 66 6f 72 6d 61 74 20 33 00. Эта последовательность байтов соответствует строке UTF-8 "SQLite format 3", включая нулевой терминатор в конце.

1.3.2. Размер страницы

Двухбайтовое значение, начинающееся с смещения 16, определяет размер страницы базы данных. Для версий SQLite 3.7.0.1 (2010-08-04) и более ранних версий это значение интерпретируется как целое число в формате big-endian и должно быть степенью двойки от 512 до 32768 включительно. Начиная с версии SQLite 3.7.1 (2010-08-23), поддерживается размер страницы 65536 байт. Значение 65536 не помещается в двухбайтовое целое число, поэтому для указания размера страницы 65536 байт значение в смещении 16 равно 0x00 0x01. Это значение можно интерпретировать как целое число 1 в формате big-endian и считать магическим числом, представляющим размер страницы 65536. Или можно рассматривать двухбайтовое поле как число в формате little-endian и сказать, что оно представляет размер страницы, деленный на 256. Эти два способа интерпретации поля размера страницы эквивалентны.

1.3.3. Версии формата файла

Версии записи формата файла и версии чтения формата файла в смещениях 18 и 19 предназначены для поддержки улучшений формата файла в будущих версиях SQLite. В текущих версиях SQLite оба этих значения равны 1 для режимов журналирования rollback и 2 для режима журналирования WAL. Если версия SQLite, соответствующая текущему спецификации формата файла, встречает файл базы данных, где версия чтения равна 1 или 2, а версия записи больше 2, то файл базы данных должен рассматриваться как только для чтения. Если встречается файл базы данных с версией чтения больше 2, то эту базу данных нельзя прочитать или записать.

1.3.4. Зарезервированные байты на странице

SQLite имеет возможность отвести небольшое количество дополнительных байтов в конце каждой страницы для использования расширениями. Эти дополнительные байты используются, например, расширением шифрования SQLite для хранения nonce и/или криптографической контрольной суммы, связанной с каждой страницей. Размер "зарезервированного пространства" в однобайтовом целом числе в смещении 20 — это количество байтов пространства в конце каждой страницы, которые следует зарезервировать для расширений. Это значение обычно равно 0. Это значение может быть нечетным.

Размер "используемого пространства" страницы базы данных — это размер страницы, указанный двухбайтовым целым числом в смещении 16 в заголовке, минус размер "зарезервированного" пространства, записанный в однобайтовом целом числе в смещении 20 в заголовке. Размер используемого пространства страницы может быть нечетным числом. Однако размер используемого пространства не может быть меньше 480. Другими словами, если размер страницы равен 512, то размер зарезервированного пространства не может превышать 32.

1.3.5. Дробные части полезной нагрузки

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

1.3.6. Счетчик изменений файла

Счетчик изменений файла — это 4-байтовое целое число в формате big-endian в смещении 24, которое увеличивается всякий раз, когда файл базы данных разблокируется после изменения. Когда два или более процессов читают один и тот же файл базы данных, каждый процесс может обнаружить изменения в базе данных от других процессов, отслеживая счетчик изменений. Процесс обычно должен очистить кэш страниц базы данных, когда другой процесс изменил базу данных, поскольку кэш стал устаревшим. Счетчик изменений файла облегчает это.

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

1.3.7. Размер базы данных в заголовке

4-байтовое целое число в формате big-endian в смещении 28 в заголовке хранит размер файла базы данных в страницах. Если этот размер базы данных в заголовке некорректен (см. следующий абзац), то размер базы данных вычисляется, анализируя фактический размер файла базы данных. Более старые версии SQLite игнорировали размер базы данных в заголовке и использовали только фактический размер файла. Более новые версии SQLite используют размер базы данных в заголовке, если он доступен, но возвращаются к фактическому размеру файла, если размер базы данных в заголовке некорректен.

Размер базы данных в заголовке считается корректным только если он не равен нулю и если 4-байтовый счетчик изменений в смещении 24 точно соответствует 4-байтовому числу, для которого действительна версия в смещении 92. Размер базы данных в заголовке всегда корректен, когда база данных изменяется только с помощью последних версий SQLite, версий 3.7.0 (2010-07-21) и более поздних. Если устаревшая версия SQLite записывает в базу данных, она не будет знать, как обновить размер базы данных в заголовке, и поэтому размер базы данных в заголовке может быть некорректным. Но устаревшие версии SQLite также оставят число, для которого действительна версия, в смещении 92 неизменным, поэтому оно не будет соответствовать счетчику изменений. Следовательно, некорректные размеры базы данных в заголовке могут быть обнаружены (и проигнорированы) путем наблюдения за тем, когда счетчик изменений не соответствует числу, для которого действительна версия.

1.3.8. Список свободных страниц

Неиспользуемые страницы в файле базы данных хранятся в списке свободных страниц. 4-байтовое целое число в формате big-endian в смещении 32 хранит номер страницы первой страницы списка свободных страниц или ноль, если список свободных страниц пуст. 4-байтовое целое число в формате big-endian в смещении 36 хранит общее количество страниц в списке свободных страниц.

1.3.9. Хеш схемы

Хеш схемы — это 4-байтовое целое число в формате big-endian в смещении 40, которое увеличивается всякий раз, когда изменяется схема базы данных. Подготовленное выражение компилируется в соответствии с определенной версией схемы базы данных. При изменении схемы базы данных выражение необходимо перекомпилировать. Когда выполняется подготовленное выражение, оно сначала проверяет хеш схемы, чтобы убедиться, что значение такое же, как при подготовке выражения, и если хеш схемы изменился, выражение либо автоматически перекомпилируется и повторно выполняется, либо завершается с ошибкой SQLITE_SCHEMA.

1.3.10. Номер формата схемы

Номер формата схемы — это 4-байтовое целое число в формате big-endian в смещении 44. Номер формата схемы аналогичен номерам версии чтения и записи формата файла в смещениях 18 и 19, за исключением того, что номер формата схемы относится к форматированию SQL высокого уровня, а не к форматированию b-дерева низкого уровня. В настоящее время определены четыре номера форматов схемы:

  1. Формат 1 понимается всеми версиями SQLite до версии 3.0.0 (2004-06-18).
  2. Формат 2 добавляет возможность строк в одной таблице иметь различное количество столбцов для поддержки функциональности ALTER TABLE ... ADD COLUMN. Поддержка чтения и записи формата 2 была добавлена в SQLite версии 3.1.3 2005-02-20.
  3. Формат 3 добавляет возможность добавления дополнительных столбцов с помощью ALTER TABLE ... ADD COLUMN с ненулевыми значениями по умолчанию. Эта возможность была добавлена в SQLite версии 3.1.4 2005-03-11.
  4. Формат 4 заставляет SQLite учитывать ключевое слово DESC в объявлениях индексов. (Ключевое слово DESC игнорируется в индексах для форматов 1, 2 и 3.) Формат 4 также добавляет два новых значения типа записи булевых данных (типы последовательности 8 и 9). Поддержка формата 4 была добавлена в SQLite 3.3.0 2006-01-10.

Новые файлы базы данных, созданные SQLite, по умолчанию используют формат 4. Предикат legacy_file_format может быть использован для создания новых файлов базы данных в формате 1. Номер версии формата можно сделать равным 1 по умолчанию вместо 4, установив SQLITE_DEFAULT_FILE_FORMAT=1 во время компиляции.

Если база данных полностью пуста, если у нее нет схемы, то номер формата схемы может быть равен нулю.

1.3.11. Рекомендуемый размер кэша

4-байтовое целое число со знаком в формате big-endian в смещении 48 — это рекомендуемый размер кэша в страницах для файла базы данных. Это только рекомендация, и SQLite не обязано ее соблюдать. Абсолютное значение целого числа используется в качестве рекомендуемого размера. Рекомендуемый размер кэша можно установить с помощью предиката default_cache_size.

1.3.12. Настройки инкрементного вакуума

Два 4-байтовых целых числа в формате big-endian в смещениях 52 и 64 используются для управления режимами auto_vacuum и incremental_vacuum. Если целое число в смещении 52 равно нулю, то страницы с картой указателей (ptrmap) пропускаются из файла базы данных, и режимы auto_vacuum и incremental_vacuum не поддерживаются. Если целое число в смещении 52 не равно нулю, то это номер страницы самой большой корневой страницы в файле базы данных, файл базы данных будет содержать страницы ptrmap, и режим должен быть либо auto_vacuum, либо incremental_vacuum. В последнем случае целое число в смещении 64 равно true для incremental_vacuum и false для auto_vacuum. Если целое число в смещении 52 равно нулю, то целое число в смещении 64 также должно быть равно нулю.

1.3.13. Кодировка текста

4-байтовое целое число в формате big-endian в смещении 56 определяет кодировку, используемую для всех текстовых строк, хранящихся в базе данных. Значение 1 означает UTF-8. Значение 2 означает UTF-16le. Значение 3 означает UTF-16be. Другие значения недопустимы. В заголовочном файле sqlite3.h определены препроцессорные макросы C — SQLITE_UTF8 как 1, SQLITE_UTF16LE как 2 и SQLITE_UTF16BE как 3 для использования вместо числовых кодов кодировки текста.

1.3.14. Номер версии пользователя

4-байтовое целое число в формате big-endian в смещении 60 — это версия пользователя, которая устанавливается и запрашивается с помощью предиката user_version. Версия пользователя не используется SQLite.

1.3.15. Идентификатор приложения

4-байтовое целое число в формате big-endian в смещении 68 — это "Идентификатор приложения", который может быть установлен командой PRAGMA application_id, чтобы идентифицировать базу данных как принадлежащую или связанную с конкретным приложением. Идентификатор приложения предназначен для файлов базы данных, используемых в качестве формата файла приложения. Идентификатор приложения может использоваться утилитами, такими как file(1), для определения конкретного типа файла, а не просто для указания "база данных SQLite3". Список назначенных идентификаторов приложений можно найти в файле magic.txt в репозитории исходного кода SQLite.

1.3.16. Номер версии библиотеки записи и номер версии, для которой это значение действительно

4-байтовое целое число в формате big-endian со смещением 96 хранит значение SQLITE_VERSION_NUMBER библиотеки SQLite, которая последней модифицировала файл базы данных. 4-байтовое целое число в формате big-endian со смещением 92 содержит значение счётчика изменений (change counter) на момент сохранения номера версии. Целое число со смещением 92 указывает, для какой транзакции номер версии действителен, и иногда называется «номером версии-действителен-для».

1.3.17. Зарезервированное пространство заголовка для расширения

Все остальные байты заголовка файла базы данных зарезервированы для будущего расширения и должны быть установлены в ноль.

1.4. Страница блока блокировки

Страница блока блокировки — единственная страница в файле базы данных, содержащая байты со смещениями от 1073741824 до 1073742335 включительно. В файлах базы данных размером не более 1073741824 байт страница блока блокировки отсутствует. В файлах базы данных размером более 1073741824 байт есть ровно одна страница блока блокировки.

Страница блока блокировки предназначена для использования операционной системой для реализации примитивов блокировки файла базы данных посредством реализации VFS. SQLite не использует страницу блока блокировки. Ядро SQLite никогда не будет читать или записывать страницу блока блокировки, хотя реализации VFS, специфичные для операционной системы, могут выбрать чтение или запись байтов на странице блока блокировки в соответствии с потребностями и особенностями используемой системы. Реализации VFS для unix и win32, встроенные в SQLite, не записывают на страницу блока блокировки, но сторонние реализации VFS для других операционных систем могут.

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

1.5. Список свободных элементов

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

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

Страница стебля списка свободных элементов состоит из массива 4-байтовых целых чисел в формате big-endian. Размер массива соответствует количеству целых чисел, помещающихся в используемом пространстве страницы. Минимальный используемый объём составляет 480 байт, поэтому массив всегда содержит не менее 120 элементов. Первое целое число на странице стебля списка свободных элементов — номер страницы следующей страницы стебля списка свободных элементов в списке или ноль, если это последняя страница стебля списка свободных элементов. Второе целое число на странице стебля списка свободных элементов — количество указателей на страницы листов списка свободных элементов, которые следуют за ним. Назовите второе целое число на странице стебля списка свободных элементов L. Если L больше нуля, то целые числа с индексами массива от 2 до L+1 включительно содержат номера страниц листов списка свободных элементов.

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

Ошибка в версиях SQLite до 3.6.0 (16.07.2008) приводила к тому, что база данных считалась повреждённой, если какие-либо из последних 6 элементов массива страницы стебля списка свободных элементов содержали ненулевые значения. Более новые версии SQLite не имеют этой проблемы. Однако более новые версии SQLite по-прежнему избегают использования последних шести элементов массива страницы стебля списка свободных элементов, чтобы файлы баз данных, созданные более новыми версиями SQLite, могли читаться более старыми версиями SQLite.

Количество страниц списка свободных элементов хранится как 4-байтовое целое число в формате big-endian в заголовке базы данных со смещением 36 от начала файла. Заголовок базы данных также хранит номер страницы первой страницы стебля списка свободных элементов как 4-байтовое целое число в формате big-endian со смещением 32 от начала файла.

1.6. Страницы дерева B

Алгоритм дерева B обеспечивает хранение ключей/данных с уникальными и упорядоченными ключами на страницах с ориентированным на страницы хранилищем. Дополнительную информацию о деревьях B см. в книге Кнута «Искусство программирования», том 3 «Сортировка и поиск», страницы 471–479. SQLite использует два варианта деревьев B. «Табличные деревья B» используют 64-битное целое число со знаком в качестве ключа и хранят все данные в листьях. «Индексные деревья B» используют произвольные ключи и не хранят никаких данных.

Страница дерева B — это либо внутренняя страница, либо лист. Листовая страница содержит ключи, а в случае табличного дерева B каждый ключ имеет связанные данные. Внутренняя страница содержит K ключей вместе с K+1 указателями на дочерние страницы дерева B. «Указатель» на внутренней странице дерева B — это просто 32-битное беззнаковое целое число — номер страницы дочерней страницы.

Количество ключей на внутренней странице дерева B, K, почти всегда не менее 2 и обычно намного больше 2. Единственное исключение — когда страница 1 является внутренней страницей дерева B. Страница 1 имеет на 100 байт меньше доступного пространства для хранения из-за наличия заголовка базы данных в начале этой страницы, и поэтому иногда (редко), если страница 1 является внутренней страницей дерева B, она может содержать только один ключ. Во всех остальных случаях K составляет 2 или больше. Верхняя граница для K — это количество ключей, которые помещаются на странице. Ключи большого размера в индексных деревьях B разбиваются на страницы переполнения, чтобы ни один ключ не занимал более одной четверти доступного пространства на странице, и таким образом каждая внутренняя страница может хранить как минимум 4 ключа. Целые ключи табличных деревьев B никогда не бывают достаточно большими, чтобы потребовать переполнение, поэтому переполнение ключей происходит только в индексных деревьях B.

Определим глубину листового дерева B как 1, а глубину любой внутренней страницы — как на 1 больше максимальной глубины любого из её дочерних элементов. В хорошо сформированной базе данных все дочерние элементы внутренней страницы имеют одинаковую глубину.

На внутренней странице дерева B указатели и ключи логически чередуются с указателем на обоих концах. (Предшествующее предложение следует понимать концептуально — фактическая структура ключей и указателей на странице сложнее и будет описана далее.) Все ключи на одной странице уникальны и логически упорядочены в порядке возрастания слева направо. (Опять же, этот порядок — логический, а не физический. Фактическое расположение ключей на странице произвольно.) Для любого ключа X указатели слева от X ссылаются на страницы дерева B, на которых все ключи меньше или равны X. Указатели справа от X ссылаются на страницы, где все ключи больше X.

На внутренней странице дерева B каждый ключ и указатель, непосредственно слева от него, объединяются в структуру, называемую «ячейкой». Правый указатель хранится отдельно. У листовой страницы дерева B нет указателей, но она всё равно использует структуру ячейки для хранения ключей для индексных деревьев B или ключей и содержимого для табличных деревьев B. Данные также содержатся в ячейке.

Каждая страница дерева B имеет, как максимум, одну родительскую страницу дерева B. Страница дерева B без родителя называется корневой страницей. Корневая страница дерева B вместе с замыканием её дочерних элементов образуют полное дерево B. Возможно (и, на самом деле, довольно распространено) иметь полное дерево B, состоящее из одной страницы, которая является одновременно листом и корнем. Поскольку существуют указатели от родителей к дочерним элементам, любую страницу полного дерева B можно найти, зная только корневую страницу. Следовательно, деревья B идентифицируются по номеру их корневой страницы.

Страница дерева B — это либо страница табличного дерева B, либо страница индексного дерева B. Все страницы в каждом полном дереве B одного типа: либо табличного, либо индексного. В файле базы данных существует одно табличное дерево B для каждой таблицы строк в схеме базы данных, включая системные таблицы, такие как sqlite_schema. В файле базы данных существует одно индексное дерево B для каждого индекса в схеме, включая предполагаемые индексы, созданные ограничениями уникальности. Нет деревьев B, связанных с виртуальными таблицами. Конкретные реализации виртуальных таблиц могут использовать дополнительные таблицы для хранения, но эти дополнительные таблицы будут иметь отдельные записи в схеме базы данных. Таблицы WITHOUT ROWID используют индексные деревья B, а не табличные деревья B, поэтому в файле базы данных имеется одно индексное дерево B для каждой таблицы WITHOUT ROWID. Дерево B, соответствующее таблице sqlite_schema, всегда является табличным деревом B и всегда имеет корневую страницу 1. Таблица sqlite_schema содержит номер корневой страницы для каждой другой таблицы и индекса в файле базы данных.

Каждая запись в табличном дереве B состоит из 64-битного целого числа со знаком в качестве ключа и до 2147483647 байт произвольных данных. (Ключ табличного дерева B соответствует rowid SQL-таблицы, которую реализует дерево B.) Внутренние табличные деревья B содержат только ключи и указатели на дочерние элементы. Все данные находятся в листьях табличного дерева B.

Каждая запись в индексном дереве B состоит из произвольного ключа длиной до 2147483647 байт и без данных.

Определим «полезную нагрузку» ячейки как произвольный по длине фрагмент ячейки. Для индексного дерева B ключ всегда произвольной длины, и поэтому полезная нагрузка — это ключ. Нет элементов произвольной длины в ячейках внутренних страниц табличного дерева B, поэтому эти ячейки не имеют полезной нагрузки. Листовые страницы табличного дерева B содержат произвольное по длине содержимое, и поэтому полезная нагрузка для ячеек на этих страницах — это содержимое.

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

Страница дерева B разделена на области в следующем порядке:

  1. Заголовок файла базы данных размером 100 байт (находится только на странице 1)
  2. Заголовок страницы дерева B размером 8 или 12 байт
  3. Массив указателей ячеек
  4. Незанятое пространство
  5. Область содержимого ячеек
  6. Зарезервированная область

Заголовок файла базы данных размером 100 байт находится только на странице 1, которая всегда является страницей табличного дерева B. Все остальные страницы дерева B в файле базы данных пропускают этот заголовок размером 100 байт.

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

Заголовок страницы дерева B имеет размер 8 байт для листовых страниц и 12 байт для внутренних страниц. Все многобайтовые значения в заголовке страницы — в формате big-endian. Заголовок страницы дерева B состоит из следующих полей:

Формат заголовка страницы дерева B
Смещение Размер Описание
0 1 Однобайтовый флаг в смещении 0, указывающий тип страницы b-дерева.
  • Значение 2 (0x02) означает, что страница является внутренней страницей индексного b-дерева.
  • Значение 5 (0x05) означает, что страница является внутренней страницей табличного b-дерева.
  • Значение 10 (0x0a) означает, что страница является листом индексного b-дерева.
  • Значение 13 (0x0d) означает, что страница является листом табличного b-дерева.
Любое другое значение для типа страницы b-дерева является ошибкой.
1 2 Двухбайтовое целое число в смещении 1 указывает начало первого свободного блока на странице или равно нулю, если свободных блоков нет.
3 2 Двухбайтовое целое число в смещении 3 указывает количество ячеек на странице.
5 2 Двухбайтовое целое число в смещении 5 определяет начало области содержимого ячейки. Нулевое значение этого целого числа интерпретируется как 65536.
7 1 Однобайтовое целое число в смещении 7 указывает количество фрагментированных свободных байтов в области содержимого ячейки.
8 4 Четырёхбайтовое число страницы в смещении 8 является правым указателем. Это значение появляется только в заголовке внутренних страниц b-дерева и отсутствует во всех остальных страницах.

Массив указателей на ячейки страницы b-дерева сразу следует за заголовком страницы b-дерева. Пусть K — количество ячеек в b-дереве. Массив указателей на ячейки состоит из K двухбайтовых целочисленных смещений к содержимому ячеек. Указатели на ячейки упорядочены по ключу, начиная с самой левой ячейки (ячейки с наименьшим ключом) и заканчивая самой правой ячейкой (ячейкой с наибольшим ключом).

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

Если страница не содержит ячеек (что возможно только для корневой страницы таблицы, не содержащей строк), то смещение к области содержимого ячеек будет равно размеру страницы минус байты зарезервированного пространства. Если база данных использует размер страницы 65536 байтов, а зарезервированное пространство равно нулю (обычное значение для зарезервированного пространства), то смещение содержимого ячейки пустой страницы должно быть 65536. Однако это целое число слишком велико, чтобы храниться в двухбайтовом беззнаковом целом числе, поэтому в качестве значения используется 0.

Свободный блок — это структура, используемая для идентификации невыделенного пространства на странице b-дерева. Свободные блоки организованы в цепочку. Первые 2 байта свободного блока — целое число в формате big-endian, которое является смещением в странице b-дерева следующего свободного блока в цепочке или равно нулю, если свободный блок последний в цепочке. Третий и четвёртый байты каждого свободного блока образуют целое число в формате big-endian, которое является размером свободного блока в байтах, включая 4-байтовый заголовок. Свободные блоки всегда соединены в порядке возрастания смещения. Второе поле заголовка страницы b-дерева — это смещение первого свободного блока или ноль, если на странице свободных блоков нет. В правильно сформированной странице b-дерева всегда должна быть хотя бы одна ячейка перед первым свободным блоком.

Для свободного блока требуется не менее 4 байт пространства. Если есть изолированная группа из 1, 2 или 3 неиспользуемых байтов в области содержимого ячейки, эти байты составляют фрагмент. Общее количество байтов во всех фрагментах хранится в пятом поле заголовка страницы b-дерева. В правильно сформированной странице b-дерева общее количество байтов в фрагментах не должно превышать 60.

Общий объём свободного места на странице b-дерева состоит из размера невыделенной области, общего размера всех свободных блоков и количества фрагментированных свободных байтов. SQLite время от времени может переупорядочивать страницу b-дерева таким образом, чтобы не было свободных блоков или фрагментированных байтов, всё неиспользуемое пространство содержалось в области невыделенного пространства, а все ячейки были плотно упакованы в конце страницы. Это называется «дефрагментацией» страницы b-дерева.

Переменная длина целого числа или «varint» — это статическое кодирование Хаффмана 64-битных целых чисел со знаком дополнения до двух, которое использует меньше места для небольших положительных значений. Varint имеет длину от 1 до 9 байт. Varint состоит либо из нуля или более байтов, у которых установлен старший бит, за которым следует один байт со сброшенным старшим битом, либо из девяти байтов, в зависимости от того, что короче. Нижние семь битов каждого из первых восьми байтов и все 8 битов девятого байта используются для восстановления 64-битного целого числа со знаком дополнения до двух. Varint хранятся в формате big-endian: биты, взятые из более раннего байта varint, более значимы, чем биты, взятые из более поздних байтов.

Формат ячейки зависит от того, на какой странице b-дерева она находится. В следующей таблице показаны элементы ячейки в порядке появления для различных типов страниц b-дерева.

Ячейка листа табличного b-дерева (заголовок 0x0d):

  • Varint, который является общим количеством байтов полезной нагрузки, включая любые переливы
  • Varint, который является целочисленным ключом, также известным как "rowid"
  • Начальная часть полезной нагрузки, которая не переливается на страницы переполнения.
  • 4-байтовое целое число big-endian, номер страницы первой страницы списка страниц переполнения — пропускается, если вся полезная нагрузка помещается на страницу b-дерева.

Внутренняя ячейка табличного b-дерева (заголовок 0x05):

  • 4-байтовое целое число big-endian, которое является левым указателем на дочернюю страницу.
  • Varint, который является целочисленным ключом

Ячейка листа индексного b-дерева (заголовок 0x0a):

  • Varint, который является общим количеством байтов полезной нагрузки ключа, включая любые переливы
  • Начальная часть полезной нагрузки, которая не переливается на страницы переполнения.
  • 4-байтовое целое число big-endian, номер страницы первой страницы списка страниц переполнения — пропускается, если вся полезная нагрузка помещается на страницу b-дерева.

Внутренняя ячейка индексного b-дерева (заголовок 0x02):

  • 4-байтовое целое число big-endian, которое является левым указателем на дочернюю страницу.
  • Varint, который является общим количеством байтов полезной нагрузки ключа, включая любые переливы
  • Начальная часть полезной нагрузки, которая не переливается на страницы переполнения.
  • 4-байтовое целое число big-endian, номер страницы первой страницы списка страниц переполнения — пропускается, если вся полезная нагрузка помещается на страницу b-дерева.

Вышеприведённую информацию можно переформулировать в табличный формат:

Формат ячеек b-дерева
Тип данных Появляется в... Описание
Лист таблицы (0x0d) Внутренняя таблица (0x05) Лист индекса (0x0a) Внутренний индекс (0x02)
4-байтовое целое число ✔ ✔ Номер страницы левого поддерева
varint ✔ ✔ ✔ Количество байтов полезной нагрузки
varint ✔ ✔ Строка
Массив байт ✔ ✔ ✔ Полезная нагрузка
4-байтовое целое число ✔ ✔ ✔ Номер страницы первой страницы переполнения

Количество полезной нагрузки, переливающейся на страницы переполнения, также зависит от типа страницы. Для следующих вычислений пусть U — это используемый размер страницы базы данных, общий размер страницы минус зарезервированное пространство в конце каждой страницы. И пусть P — размер полезной нагрузки. В дальнейшем символ X представляет максимальное количество полезной нагрузки, которое может быть хранено непосредственно на странице b-дерева без перелива на страницу переполнения, а символ M представляет минимальное количество полезной нагрузки, которое должно быть хранится на странице b-дерева, прежде чем будет разрешено переполнение.

Ячейка листа табличного b-дерева:

Пусть X будет U-35. Если размер полезной нагрузки P меньше или равен X, то вся полезная нагрузка хранится на странице листа табличного b-дерева. Пусть M будет ((U-12)*32/255)-23, а K будет M+((P-M)%(U-4)). Если P больше X, то количество байтов, хранящихся на странице листа табличного b-дерева, равно K, если K меньше или равно X, или M в противном случае. Количество байтов, хранящихся на странице листа, никогда не меньше M.

Внутренняя ячейка табличного b-дерева:

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

Ячейка листа или внутренняя ячейка индексного b-дерева:

Пусть X будет ((U-12)*64/255)-23. Если размер полезной нагрузки P меньше или равен X, то вся полезная нагрузка хранится на странице b-дерева. Пусть M будет ((U-12)*32/255)-23, а K будет M+((P-M)%(U-4)). Если P больше X, то количество байтов, хранящихся на странице индексного b-дерева, равно K, если K меньше или равно X, или M в противном случае. Количество байтов, хранящихся на странице индекса, никогда не меньше M.

Вот альтернативное описание тех же вычислений:

  • X — это U-35 для страниц листа табличного b-дерева или ((U-12)*64/255)-23 для страниц индекса.
  • M всегда равно ((U-12)*32/255)-23.
  • Пусть K будет M+((P-M)%(U-4)).
  • Если P≤X, то все P байтов полезной нагрузки хранятся непосредственно на странице b-дерева без переполнения.
  • Если P>X и K≤X, то первые K байтов P хранятся на странице b-дерева, а оставшиеся P-K байты хранятся на страницах переполнения.
  • Если P>X и K>X, то первые M байтов P хранятся на странице b-дерева, а оставшиеся P-M байты хранятся на страницах переполнения.

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

1.7. Страницы переполнения полезной нагрузки ячеек

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

1.8. Страницы карты указателей или страниц ptrmap

Страницы карты указателей или страниц ptrmap — это дополнительные страницы, вставленные в базу данных, чтобы сделать работу режимов auto_vacuum и incremental_vacuum более эффективной. Другие типы страниц в базе данных обычно имеют указатели от родительской к дочерней. Например, внутренняя страница b-дерева содержит указатели на её дочерние страницы b-дерева, а цепочка переполнения имеет указатель от более ранних к более поздним ссылкам в цепочке. Страница ptrmap содержит информацию о связях, направленную в противоположном направлении, от дочерней к родительской.

Страницы ptrmap должны существовать в любом файле базы данных, который имеет ненулевое максимальное значение страницы корневого b-дерева в смещении 52 в заголовке базы данных. Если максимальное значение страницы корневого b-дерева равно нулю, то база данных не должна содержать страниц ptrmap.

В базе данных со страницами ptrmap первой является страница 2. Страница ptrmap состоит из массива записей по 5 байт. Пусть J — количество записей по 5 байт, которые поместятся в используемом пространстве страницы. (Другими словами, J=U/5.) Первая страница ptrmap будет содержать информацию о обратных ссылках для страниц с 3 по J+2 включительно. Вторая страница ptrmap будет на странице J+3 и эта страница ptrmap будет предоставлять информацию об обратных ссылках для страниц с J+4 по 2*J+3 включительно. И так далее для всего файла базы данных.

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

Каждая запись по 5 байт на странице ptrmap предоставляет информацию об обратной ссылке на одну из страниц, которые непосредственно следуют за страницей указателя. Если страница B — страница ptrmap, то информация об обратной ссылке на страницу B+1 предоставляется первой записью на странице указателя. Информация о странице B+2 предоставляется второй записью. И так далее.

Каждая запись ptrmap по 5 байт состоит из одного байта информации о «типе страницы» и 4-байтного номера страницы в формате big-endian. Распознаются пять типов страниц:

  1. Страница корня b-дерева. Номер страницы должен быть нулем.
  2. Страница freelist. Номер страницы должен быть нулем.
  3. Первая страница цепочки переполнения полезной нагрузки ячейки. Номер страницы — это страница b-дерева, которая содержит ячейку, содержимое которой переполнилось.
  4. Страница в цепочке переполнения, отличная от первой страницы. Номер страницы — это предыдущая страница цепочки переполнения.
  5. Страница b-дерева, не являющаяся корневой. Номер страницы — это родительская страница b-дерева.

В любом файле базы данных, который содержит страницы ptrmap, все страницы корней b-деревьев должны предшествовать любым страницам некорневых страниц b-дерева, страницам переполнения полезной нагрузки ячеек или страницам freelist. Это ограничение гарантирует, что страница корня никогда не будет перемещена во время автоматического или инкрементного вакуума. Логика автоматического вакуума не знает, как обновить поле root_page таблицы sqlite_schema, и поэтому необходимо предотвратить перемещение страниц корней во время автоматического вакуума, чтобы сохранить целостность таблицы sqlite_schema. Страницы корней перемещаются в начало файла базы данных операциями CREATE TABLE, CREATE INDEX, DROP TABLE и DROP INDEX.

2. Слой схемы

Текст выше описывает низкоуровневые аспекты формата файла SQLite. Механизм b-дерева обеспечивает мощный и эффективный способ доступа к большому набору данных. В этом разделе будет описано, как низкоуровневый слой b-дерева используется для реализации возможностей SQL высокого уровня.

2.1. Формат записи

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

Полезная нагрузка, либо данные b-дерева таблицы, либо ключи b-дерева индекса, всегда находятся в «формате записи». Формат записи определяет последовательность значений, соответствующих столбцам в таблице или индексе. Формат записи определяет количество столбцов, тип данных каждого столбца и содержимое каждого столбца.

Формат записи широко использует представление целых чисел переменной длины или varint для 64-битных знакомых целых чисел, определённых выше.

Запись содержит заголовок и тело в этом порядке. Заголовок начинается с одного varint, который определяет общее количество байтов в заголовке. Значение varint — это размер заголовка в байтах, включая само значение varint размера. После значения varint размера следуют одна или несколько дополнительных varint, по одной на каждый столбец. Эти дополнительные varint называются «номерами типа последовательности» и определяют тип данных каждого столбца в соответствии со следующей таблицей:

Коды типов последовательности формата записи
Тип последовательности Размер содержимого Значение
0 0 Значение — NULL.
1 1 Значение — 8-битное целое число со знаком дополнения до двух.
2 2 Значение — 16-битное целое число со знаком дополнения до двух в формате big-endian.
3 3 Значение — 24-битное целое число со знаком дополнения до двух в формате big-endian.
4 4 Значение — 32-битное целое число со знаком дополнения до двух в формате big-endian.
5 6 Значение — 48-битное целое число со знаком дополнения до двух в формате big-endian.
6 8 Значение — 64-битное целое число со знаком дополнения до двух в формате big-endian.
7 8 Значение — 64-битное число с плавающей запятой IEEE 754-2008 в формате big-endian.
8 0 Значение — целое число 0. (Доступно только для формата схемы 4 и выше.)
9 0 Значение — целое число 1. (Доступно только для формата схемы 4 и выше.)
10,11 переменная Зарезервировано для внутреннего использования. Эти коды типов последовательности никогда не будут появляться в правильно сформированном файле базы данных, но они могут использоваться во временных файлах базы данных, которые SQLite иногда генерирует для собственного использования. Значения этих кодов могут меняться от одной версии SQLite к другой.
N≥12 и чётное (N-12)/2 Значение — BLOB размером (N-12)/2 байта.
N≥13 и нечётное (N-13)/2 Значение — строка в кодировке текста и длиной (N-13)/2 байта. Терминатор нуля не хранится.

Значение varint размера заголовка и значения varint типов последовательности обычно состоят из одного байта. Значения varint типов последовательности для больших строк и BLOB могут расширяться до двух или трёх байтовых значений varint, но это скорее исключение, чем правило. Формат varint очень эффективен при кодировании заголовка записи.

Значения для каждого столбца в записи непосредственно следуют за заголовком. Для типов последовательности 0, 8, 9, 12 и 13 длина значения равна нулю байтов. Если все столбцы относятся к этим типам, то раздел тела записи пуст.

В записи может быть меньше значений, чем число столбцов в соответствующей таблице. Это может произойти, например, после выполнения SQL-выражения ALTER TABLE ... ADD COLUMN, которое увеличило число столбцов в схеме таблицы без изменения существующих строк в таблице. Пропущенные значения в конце записи заполняются значением по умолчанию для соответствующих столбцов, определённых в схеме таблицы.

2.2. Порядок сортировки записей

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

  1. Значения NULL (тип последовательности 0) сортируются первыми.
  2. Числовые значения (типы последовательности от 1 до 9) сортируются после значений NULL в числовом порядке.
  3. Текстовые значения (нечётные типы последовательности 13 и выше) сортируются после числовых значений в порядке, определяемом функцией сортировки столбцов collating function.
  4. Значения BLOB (чётные типы последовательности 12 и выше) сортируются последними и в порядке, определяемом memcmp().

Для вычисления порядка текстовых полей необходима функция сортировки collating function для каждого столбца. SQLite определяет три встроенные функции сортировки:

BINARY Встроенная сортировка BINARY сравнивает строки побайтово, используя функцию memcmp() из стандартной библиотеки C.
NOCASE Сортировка NOCASE аналогична BINARY, за исключением того, что заглавные ASCII-символы ('A' до 'Z') преобразуются в соответствующие строчные символы перед выполнением сравнения. Преобразование регистра выполняется только для ASCII-символов. NOCASE не реализует универсальное безрегистровое сравнение Unicode.
RTRIM RTRIM аналогична BINARY, за исключением того, что дополнительные пробелы в конце любой строки не изменяют результат. Другими словами, строки будут сравниваться как равные, если они отличаются только количеством пробелов в конце.

Дополнительные функции сортировки, специфичные для приложения, можно добавить в SQLite с помощью интерфейса sqlite3_create_collation().

Функция сортировки по умолчанию для всех строк — BINARY. Альтернативные функции сортировки для столбцов таблицы можно указать в операторе CREATE TABLE с помощью предложения COLLATE в определении столбца column definition. Когда столбец индексируется, по умолчанию используется та же функция сортировки, указанная в операторе CREATE TABLE, для столбца в индексе, хотя это можно переопределить с помощью предложения COLLATE в операторе CREATE INDEX.

2.3. Представление SQL-таблиц

Каждая обычная SQL-таблица в схеме базы данных представлена в файле b-деревом таблицы. Каждая запись в b-дереве таблицы соответствует строке SQL-таблицы. rowid SQL-таблицы — это 64-битное знаковое целое число, являющееся ключом каждой записи в b-дереве таблицы.

Содержимое каждой строки SQL-таблицы хранится в файле базы данных путём сначала объединения значений различных столбцов в массив байтов в формате записи, а затем хранения этого массива байтов в качестве полезной нагрузки в записи b-дерева таблицы. Порядок значений в записи такой же, как и порядок столбцов в определении SQL-таблицы. Когда SQL-таблица содержит столбец INTEGER PRIMARY KEY (который является псевдонимом rowid), этот столбец появляется в записи как значение NULL. SQLite всегда будет использовать ключ b-дерева таблицы, а не значение NULL, при ссылке на столбец INTEGER PRIMARY KEY.

Если affinity столбца — REAL, и этот столбец содержит значение, которое можно преобразовать в целое число без потери информации (если значение не содержит дробной части и не слишком велико для представления целым числом), то столбец может храниться в записи как целое число. SQLite преобразует значение обратно в число с плавающей запятой при извлечении его из записи.

2.4. Представление таблиц WITHOUT ROWID

Если таблица SQL создана с использованием фрагмента «WITHOUT ROWID» в конце оператора CREATE TABLE, то эта таблица является таблицей WITHOUT ROWID и использует другое представление на диске. Таблица WITHOUT ROWID использует индексное b-дерево, а не табличное b-дерево для хранения. Ключ для каждой записи в индексном b-дереве WITHOUT ROWID представляет собой запись, состоящую из столбцов PRIMARY KEY, за которыми следуют все остальные столбцы таблицы. Столбцы первичного ключа появляются в том порядке, в котором они были объявлены в предложении PRIMARY KEY, а оставшиеся столбцы появляются в порядке их следования в операторе CREATE TABLE.

Следовательно, кодирование содержимого таблицы WITHOUT ROWID такое же, как и кодирование содержимого обычной таблицы с rowid, за исключением того, что порядок столбцов переупорядочен таким образом, что столбцы PRIMARY KEY появляются первыми, и содержимое используется в качестве ключа в индексном b-дереве, а не в качестве данных в табличном b-дереве. Специальные правила кодирования для столбцов с аффинностью REAL применяются к таблицам WITHOUT ROWID так же, как и к таблицам с rowid.

2.4.1. Исключение избыточных столбцов в первичном ключе таблиц WITHOUT ROWID

Если первичный ключ таблицы WITHOUT ROWID использует одни и те же столбцы с одной и той же последовательностью сортировки более одного раза, то последующие вхождения этого столбца в определение первичного ключа игнорируются. Например, следующие операторы CREATE TABLE все определяют одну и ту же таблицу, которая будет иметь точно такое же представление на диске:

CREATE TABLE t1(a,b,c,d,PRIMARY KEY(a,c)) WITHOUT ROWID;
CREATE TABLE t1(a,b,c,d,PRIMARY KEY(a,c,a,c)) WITHOUT ROWID;
CREATE TABLE t1(a,b,c,d,PRIMARY KEY(a,A,a,C)) WITHOUT ROWID;
CREATE TABLE t1(a,b,c,d,PRIMARY KEY(a,a,a,a,c)) WITHOUT ROWID;

Первый пример выше, конечно, является предпочтительным определением таблицы. Все примеры создают таблицу WITHOUT ROWID с двумя столбцами PRIMARY KEY, «a» и «c», в этом порядке, за которыми следуют два столбца данных «b» и «d», также в этом порядке.

2.5. Представление индексов SQL

Каждый индекс SQL, явно объявленный с помощью оператора CREATE INDEX или подразумеваемый ограничением UNIQUE или PRIMARY KEY, соответствует индексному b-дереву в файле базы данных. Каждая запись в индексном b-дереве соответствует одной строке в связанной таблице SQL. Ключ индексного b-дерева — это запись, составленная из столбцов, которые индексируются, за которыми следует ключ соответствующей строки таблицы. Для обычных таблиц ключом строки является rowid, а для таблиц WITHOUT ROWID ключом строки является PRIMARY KEY. Поскольку каждая строка в таблице имеет уникальный ключ строки, все ключи в индексе уникальны.

В нормальном индексе существует взаимно однозначное соответствие между строками таблицы и записями в каждом индексе, связанном с этой таблицей. Однако в частичном индексе индексное b-дерево содержит только записи, соответствующие строкам таблицы, для которых выражение условия WHERE в операторе CREATE INDEX истинно. Соответствующие строки в индексном и табличном b-деревьях имеют одинаковые значения rowid или первичного ключа и содержат одинаковые значения для всех индексированных столбцов.

2.5.1. Исключение избыточных столбцов в вторичных индексах WITHOUT ROWID

В индексе таблицы WITHOUT ROWID, если столбец PRIMARY KEY также является столбцом в индексе и имеет соответствующую последовательность сортировки, то индексированный столбец не повторяется в суффиксе ключа таблицы в конце записи индекса. Рассмотрим следующий SQL:

CREATE TABLE ex25(a,b,c,d,e,PRIMARY KEY(d,c,a)) WITHOUT rowid;
CREATE INDEX ex25ce ON ex25(c,e);
CREATE INDEX ex25acde ON ex25(a,c,d,e);
CREATE INDEX ex25ae ON ex25(a COLLATE nocase,e);

Каждая строка в индексе ex25ce — это запись со следующими столбцами: c, e, d, a. Первые два столбца — это индексированные столбцы c и e. Остальные столбцы — первичный ключ соответствующей строки таблицы. Обычно первичный ключ включал бы столбцы d, c и a, но поскольку столбец c уже появляется ранее в индексе, он опущена из суффикса ключа.

В крайнем случае, когда индексированные столбцы охватывают все столбцы PRIMARY KEY, индекс будет состоять только из индексированных столбцов. Пример ex25acde выше демонстрирует это. Каждая запись в индексе ex25acde состоит только из столбцов a, c, d и e в этом порядке.

Каждая строка в ex25ae содержит пять столбцов: a, e, d, c, a. Столбец «a» повторяется, поскольку первое вхождение «a» имеет функцию сортировки «nocase», а второе — последовательность сортировки «binary». Если столбец «a» не повторяется, а таблица содержит две или более записи с одинаковым значением «e» и где «a» отличается только регистром, все эти записи таблицы будут соответствовать одной записи в индексе, что нарушит взаимно однозначное соответствие между таблицей и индексом.

Исключение избыточных столбцов в суффиксе ключа записи индекса происходит только в таблицах WITHOUT ROWID. В обычной таблице с rowid запись индекса всегда заканчивается rowid, даже если столбец INTEGER PRIMARY KEY является одним из индексированных столбцов.

2.6. Хранение схемы базы данных SQL

Первая страница файла базы данных — это корневая страница табличного b-дерева, содержащего специальную таблицу с именем «sqlite_schema». Это b-дерево известно как «таблица схемы», поскольку оно хранит полную схему базы данных. Структура таблицы sqlite_schema такая, как если бы она была создана с помощью следующего SQL:

CREATE TABLE sqlite_schema(
  type text,
  name text,
  tbl_name text,
  rootpage integer,
  sql text
);

Таблица sqlite_schema содержит одну строку для каждой таблицы, индекса, представления и триггера (в совокупности «объекты») в схеме базы данных, за исключением того, что нет записи для самой таблицы sqlite_schema. Таблица sqlite_schema содержит записи для внутренних объектов схемы помимо определенных пользователем и программистом объектов.

Столбец sqlite_schema.type будет содержать один из следующих текстовых строк: 'table', 'index', 'view' или 'trigger' в зависимости от типа определенного объекта. Строка 'table' используется как для обычных, так и для виртуальных таблиц.

Столбец sqlite_schema.name будет содержать имя объекта. Ограничения UNIQUE и PRIMARY KEY для таблиц заставляют SQLite создавать внутренние индексы с именами вида «sqlite_autoindex_TABLE_N», где TABLE заменяется именем таблицы, содержащей ограничение, а N — целое число, начинающееся с 1 и увеличивающееся на 1 с каждым ограничением, увиденным в определении таблицы. В таблице WITHOUT ROWID нет записи sqlite_schema для PRIMARY KEY, но имя «sqlite_autoindex_TABLE_N» откладывается для PRIMARY KEY так, как если бы запись sqlite_schema существовала. Это повлияет на нумерацию последующих ограничений UNIQUE. Имя «sqlite_autoindex_TABLE_N» никогда не выделяется для INTEGER PRIMARY KEY, ни в таблицах rowid, ни в таблицах WITHOUT ROWID.

Столбец sqlite_schema.tbl_name содержит имя таблицы или представления, к которому относится объект. Для таблицы или представления столбец tbl_name является копией столбца name. Для индекса tbl_name — это имя таблицы, которая индексируется. Для триггера столбец tbl_name хранит имя таблицы или представления, которое вызывает срабатывание триггера.

Столбец sqlite_schema.rootpage хранит номер страницы корневой страницы b-дерева для таблиц и индексов. Для строк, определяющих представления, триггеры и виртуальные таблицы, столбец rootpage имеет значение 0 или NULL.

Столбец sqlite_schema.sql хранит текстовое представление SQL, описывающее объект. Этот текст SQL представляет собой оператор CREATE TABLE, CREATE VIRTUAL TABLE, CREATE INDEX, CREATE VIEW или CREATE TRIGGER, который, если его применить к файлу базы данных, когда это основная база данных соединения базы данных, позволит восстановить объект. Текст обычно является копией исходного оператора, используемого для создания объекта, но с примененными нормализациями, чтобы текст соответствовал следующим правилам:

  • Ключевые слова CREATE, TABLE, VIEW, TRIGGER и INDEX в начале оператора преобразуются в прописные буквы.
  • Ключевые слова TEMP или TEMPORARY удаляются, если они встречаются после ключевого слова CREATE.
  • Любой квалификатор имени базы данных, который встречается перед именем создаваемого объекта, удаляется.
  • Удаляются начальные пробелы.
  • Все пробелы после первых двух ключевых слов преобразуются в один пробел.

Текст в столбце sqlite_schema.sql является копией исходного текста оператора CREATE, который создал объект, за исключением нормализации, как описано выше, и изменений, внесенных последующими операторами ALTER TABLE. sqlite_schema.sql имеет значение NULL для внутренних индексов, которые автоматически создаются ограничениями UNIQUE или PRIMARY KEY.

2.6.1. Альтернативные имена таблицы схемы

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

  1. sqlite_master
  2. sqlite_temp_schema
  3. sqlite_temp_master

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

2.6.2. Внутренние объекты схемы

В дополнение к таблицам, индексам, представлениям и триггерам, созданным приложением и/или разработчиком с помощью операторов CREATE, таблица sqlite_schema может содержать ноль или более записей для внутренних объектов схемы, создаваемых SQLite для собственного внутреннего использования. Имена внутренних объектов схемы всегда начинаются с «sqlite_», и любая таблица, индекс, представление или триггер, имя которых начинается с «sqlite_», является внутренним объектом схемы. SQLite запрещает приложениям создавать объекты с именами, начинающимися с «sqlite_».

Внутренние объекты схемы, используемые SQLite, могут включать следующие:

  • Индексы с именами вида «sqlite_autoindex_TABLE_N», которые используются для реализации ограничений UNIQUE и PRIMARY KEY для обычных таблиц.

  • Таблица с именем «sqlite_sequence», используемая для отслеживания максимального исторического INTEGER PRIMARY KEY для таблицы, использующей AUTOINCREMENT.

  • Таблицы с именами вида «sqlite_statN», где N — целое число. Такие таблицы хранят статистические данные базы данных, собранные командой ANALYZE и используемые планировщиком запросов для определения наилучшего алгоритма для каждого запроса.

В будущих выпусках в формат файла SQLite могут быть добавлены новые имена внутренних объектов схемы, всегда начинающиеся с «sqlite_».

2.6.3. Таблица sqlite_sequence

Таблица sqlite_sequence — это внутренняя таблица, используемая для реализации AUTOINCREMENT. Таблица sqlite_sequence создается автоматически при создании любой обычной таблицы с целочисленным первичным ключом AUTOINCREMENT. После создания таблица sqlite_sequence существует в таблице sqlite_schema навсегда; ее нельзя удалить. Схема таблицы sqlite_sequence:

CREATE TABLE sqlite_sequence(name,seq);

В таблице sqlite_sequence есть одна строка для каждой обычной таблицы, использующей AUTOINCREMENT. Имя таблицы (как оно отображается в sqlite_schema.name) находится в поле sqlite_sequence.name, а наибольшее значение INTEGER PRIMARY KEY, когда-либо вставленное в эту таблицу, находится в поле sqlite_sequence.seq. Новые автоматически сгенерированные целочисленные первичные ключи для таблиц AUTOINCREMENT гарантированно будут больше, чем значение в поле sqlite_sequence.seq для этой таблицы. Если поле sqlite_sequence.seq таблицы AUTOINCREMENT уже содержит максимальное целочисленное значение (9223372036854775807), то попытки добавить новые строки в эту таблицу с автоматически сгенерированным целочисленным первичным ключом завершатся ошибкой SQLITE_FULL. Поле sqlite_sequence.seq автоматически обновляется при необходимости при вставке новых записей в таблицу AUTOINCREMENT. Строка sqlite_sequence для таблицы AUTOINCREMENT автоматически удаляется при удалении таблицы. Если строка sqlite_sequence для таблицы AUTOINCREMENT не существует при обновлении таблицы AUTOINCREMENT, то создается новая строка sqlite_sequence. Если значение sqlite_sequence.seq для таблицы AUTOINCREMENT вручную установлено не на целое число, а затем происходит попытка вставки или обновления таблицы AUTOINCREMENT, поведение не определено.

Код приложения может изменять таблицу sqlite_sequence, добавлять новые строки, удалять строки или изменять существующие строки. Однако код приложения не может создать таблицу sqlite_sequence, если она не существует. Код приложения может удалить все записи из таблицы sqlite_sequence, но код приложения не может удалить таблицу sqlite_sequence.

2.6.4. Таблица sqlite_stat1

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

CREATE TABLE sqlite_stat1(tbl,idx,stat);

Обычно в таблице sqlite_stat1 одна строка на каждый индекс, при этом индекс идентифицируется именем в столбце sqlite_stat1.idx. Столбец sqlite_stat1.tbl содержит имя таблицы, к которой относится индекс. В каждой такой строке столбец sqlite_stat.stat будет строкой, состоящей из списка целых чисел, за которыми могут следовать один или несколько аргументов. Первое целое число в этом списке — приблизительное количество строк в индексе. (Количество строк в индексе такое же, как количество строк в таблице, за исключением частичных индексов.) Второе целое число — приблизительное количество строк в индексе, имеющих одинаковое значение в первом столбце индекса. Третье целое число — количество строк в индексе, имеющих одинаковое значение для первых двух столбцов. N-е целое число (для N > 1) — это приближенное среднее количество строк в индексе, имеющих одинаковое значение для первых N-1 столбцов. Для индекса из K столбцов в столбце stat будет K + 1 целое число. Если индекс уникальный, то последнее целое число будет равно 1.

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

Если присутствует аргумент «unordered», планировщик запросов предполагает, что индекс неупорядоченный и не будет использовать его для диапазонного запроса или сортировки.

Аргумент «sz=NNN» (где NNN — последовательность из одного или более цифр) означает, что средний размер строки во всех записях таблицы или индекса составляет NNN байт на строку. Планировщик запросов SQLite может использовать информацию об оцененном размере строки, предоставляемую маркером «sz=NNN», для выбора более маленьких таблиц и индексов, которые требуют меньше операций ввода-вывода с диска.

Наличие маркера «noskipscan» в поле sqlite_stat1.stat для индекса предотвращает использование этого индекса с оптимизацией skip-scan.

В будущем могут быть добавлены новые текстовые маркеры в конец столбца stat. Для совместимости нераспознанные маркеры в конце столбца stat игнорируются.

Если столбец sqlite_stat1.idx имеет значение NULL, то столбец sqlite_stat1.stat содержит одно целое число, которое представляет приблизительное количество строк в таблице, идентифицируемой sqlite_stat1.tbl. Если столбец sqlite_stat1.idx совпадает со столбцом sqlite_stat1.tbl, то таблица является таблицей WITHOUT ROWID, и поле sqlite_stat1.stat содержит информацию о индексном дереве B-дерева, реализующем таблицу WITHOUT ROWID.

2.6.5. Таблица sqlite_stat2

Таблица sqlite_stat2 создается и используется только в том случае, если SQLite скомпилирован с SQLITE_ENABLE_STAT2 и если версия SQLite находится в диапазоне от 3.6.18 (2009-09-11) до 3.7.8 (2011-09-19). Таблица sqlite_stat2 не читается и не записывается ни одной версией SQLite до 3.6.18 или после 3.7.8. Таблица sqlite_stat2 содержит дополнительную информацию о распределении ключей внутри индекса.

CREATE TABLE sqlite_stat2(tbl,idx,sampleno,sample);

Столбцы sqlite_stat2.idx и sqlite_stat2.tbl в каждой строке таблицы sqlite_stat2 идентифицируют индекс, описываемый этой строкой. Обычно в таблице sqlite_stat2 10 строк на каждый индекс.

Записи sqlite_stat2 для индекса, у которых sqlite_stat2.sampleno находится в диапазоне от 0 до 9 включительно, являются выборками левого крайнего значения ключа в индексе, взятыми в равномерно распределённых точках вдоль индекса. Пусть C — количество строк в индексе. Тогда выбираемые строки задаются формулой:

rownumber = (i*C*2 + C)/20

Переменная i в предыдущем выражении изменяется от 0 до 9. По сути, пространство индекса разделяется на 10 равномерных ведер, а выборки — это средняя строка из каждого ведра.

Формат sqlite_stat2 записан здесь для справки в случае несовместимости. В последних версиях SQLite таблица sqlite_stat2 больше не поддерживается, и если она существует, она просто игнорируется.

2.6.6. Таблица sqlite_stat3

Таблица sqlite_stat3 используется только в том случае, если SQLite скомпилирован с SQLITE_ENABLE_STAT3 или SQLITE_ENABLE_STAT4 и если версия SQLite 3.7.9 (2011-11-01) или выше. Таблица sqlite_stat3 не читается и не записывается ни одной версией SQLite до 3.7.9. Если опция компиляции SQLITE_ENABLE_STAT4 используется и версия SQLite 3.8.1 (2013-10-17) или выше, то sqlite_stat3 может читаться, но не записываться. Таблица sqlite_stat3 содержит дополнительную информацию о распределении ключей в индексе, информацию, которую планировщик запросов может использовать для разработки более эффективных и быстрых алгоритмов запросов. Схема таблицы sqlite_stat3 следующая:

CREATE TABLE sqlite_stat3(tbl,idx,nEq,nLt,nDLt,sample);

В таблице sqlite_stat3 обычно несколько записей для каждого индекса. Столбец sqlite_stat3.sample содержит значение левого крайнего поля индекса, идентифицируемого sqlite_stat3.idx и sqlite_stat3.tbl. Столбец sqlite_stat3.nEq содержит приблизительное число записей в индексе, у которых левое крайнее поле точно соответствует выборке. Столбец sqlite_stat3.nLt содержит приблизительное число записей в индексе, у которых левое крайнее поле меньше выборки. Столбец sqlite_stat3.nDLt содержит приблизительное количество различных левых крайних записей в индексе, которые меньше выборки.

В таблице может быть произвольное количество записей sqlite_stat3 для каждого индекса. Команда ANALYZE обычно генерирует таблицы sqlite_stat3, которые содержат от 10 до 40 выборок, распределённых по ключевому пространству и имеющих большие значения nEq.

В корректно сформированной таблице sqlite_stat3 выборки для любого отдельного индекса должны появляться в том же порядке, в котором они встречаются в индексе. Другими словами, если запись с левым крайним полем S1 расположена раньше в индексном дереве B-дерева, чем запись с левым крайним полем S2, то в таблице sqlite_stat3 выборка S1 должна иметь меньший идентификатор строки, чем выборка S2.

2.6.7. Таблица sqlite_stat4

Таблица sqlite_stat4 создается и используется только в том случае, если SQLite скомпилирован с SQLITE_ENABLE_STAT4 и если версия SQLite 3.8.1 (2013-10-17) или выше. Таблица sqlite_stat4 не читается и не записывается ни одной версией SQLite до 3.8.1. Таблица sqlite_stat4 содержит дополнительную информацию о распределении ключей внутри индекса или о распределении ключей в первичном ключе таблицы WITHOUT ROWID. Планировщик запросов может иногда использовать дополнительную информацию в таблице sqlite_stat4 для разработки более эффективных и быстрых алгоритмов запросов. Схема таблицы sqlite_stat4:

CREATE TABLE sqlite_stat4(tbl,idx,nEq,nLt,nDLt,sample);

В таблице sqlite_stat4 обычно от 10 до 40 записей для каждого индекса, для которого доступна статистика, однако эти ограничения не являются жёсткими.

tbl: Столбец sqlite_stat4.tbl содержит имя таблицы, которой принадлежит индекс, описываемый строкой.
idx: Столбец sqlite_stat4.idx содержит имя индекса, описываемого строкой, или, в случае записи sqlite_stat4 для таблицы WITHOUT ROWID, имя самой таблицы.
sample: Столбец sqlite_stat4.sample содержит BLOB в формате записи, который кодирует индексированные столбцы, за которыми следует rowid для таблицы rowid или столбцы первичного ключа для таблицы WITHOUT ROWID. BLOB sqlite_stat4.sample для самой таблицы WITHOUT ROWID содержит только столбцы первичного ключа. Пусть число столбцов, закодированных в BLOB sqlite_stat4.sample, равно N. Для индексов обычной таблицы rowid N будет на один больше, чем количество индексированных столбцов. Для индексов таблиц WITHOUT ROWID N будет равно числу индексированных столбцов плюс числу столбцов в первичном ключе. Для таблицы WITHOUT ROWID N будет равно числу столбцов в первичном ключе.
nEq: Столбец sqlite_stat4.nEq содержит список из N целых чисел, где K-е целое число — приблизительное число записей в индексе, у которых K левых столбцов точно совпадают с K левыми столбцами образца.
nLt: Столбец sqlite_stat4.nLt содержит список из N целых чисел, где K-е целое число — приблизительное число записей в индексе, у которых K левых столбцов в совокупности меньше, чем K левых столбцов образца.
nDLt: Столбец sqlite_stat4.nDLt содержит список из N целых чисел, где K-е целое число — приблизительное число записей в индексе, которые отличаются в первых K столбцах, и где K левых столбцов в совокупности меньше, чем K левых столбцов образца.

sqlite_stat4 — это обобщение таблицы sqlite_stat3. Таблица sqlite_stat3 предоставляет информацию о левом столбце индекса, тогда как таблица sqlite_stat4 предоставляет информацию обо всех столбцах индекса.

Может быть произвольное количество записей sqlite_stat4 на индекс. Команда ANALYZE обычно генерирует таблицы sqlite_stat4, которые содержат от 10 до 40 образцов, распределенных по ключевому пространству и с большими значениями nEq.

В правильно сформированной таблице sqlite_stat4 образцы для любого отдельного индекса должны появляться в том же порядке, в котором они встречаются в индексе. Другими словами, если запись S1 раньше в дереве b-дерева индекса, чем запись S2, то в таблице sqlite_stat4 образец S1 должен иметь меньший rowid, чем образец S2.

3. Журнал отката

Журнал отката — это файл, связанный с каждым файлом базы данных SQLite, который содержит информацию, используемую для восстановления файла базы данных в исходное состояние в ходе транзакции. Файл журнала отката всегда находится в той же директории, что и файл базы данных, и имеет то же имя, что и файл базы данных, но со строкой "-journal" в конце. Только один журнал отката может быть связан с данной базой данных, а значит, только одна транзакция записи может быть открыта для одной базы данных в одно время.

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

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

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

  1. Файл журнала отката может быть удалён,
  2. Файл журнала отката может быть обнулён, или
  3. Заголовок журнала отката может быть перезаписан недействительным заголовком текста (например, все нули).

Эти три способа завершения транзакции соответствуют настройкам DELETE, TRUNCATE и PERSIST параметра journal_mode pragma, соответственно.

Действительный журнал отката начинается с заголовка в следующем формате:

Формат заголовка журнала отката
Смещение Размер Описание
0 8 Строка заголовка: 0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd7
8 4 «Количество страниц» — количество страниц в следующем сегменте журнала или -1, чтобы означать всё содержимое до конца файла
12 4 Случайный nonce для контрольной суммы
16 4 Изначальный размер базы данных в страницах
20 4 Размер сектора диска, предполагаемый процессом, который записал этот журнал.
24 4 Размер страниц в этом журнале.

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

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

Пусть размер страницы базы данных (значение целого числа по смещению 24 в заголовке журнала) равно N. Тогда формат записи страницы журнала следующий:

Формат записи страницы журнала отката
Смещение Размер Описание
0 4 Номер страницы в файле базы данных
4 N Исходное содержимое страницы до начала транзакции
N+4 4 Контрольная сумма

Контрольная сумма — целое 32-битное число без знака, вычисляемое следующим образом:

  1. Инициализировать контрольную сумму значением контрольной суммы nonce, найденной в заголовке журнала по смещению 12.
  2. Инициализировать индекс X значением N-200 (где N — размер страницы базы данных в байтах).
  3. Интерпретировать байт по смещению X в странице как 8-битное целое число без знака и добавить значение этого целого числа к контрольной сумме.
  4. Вычесть 200 из X.
  5. Если X больше или равен нулю, вернуться к шагу 3.

Значение контрольной суммы используется для защиты от неполных записей записи страницы журнала после сбоя питания. Каждый раз, когда начинается транзакция, используется другой случайный nonce, чтобы свести к минимуму риск того, что незаписанные сектора могут случайно содержать данные с той же страницы, которая была частью предыдущих журналов. Изменяя nonce для каждой транзакции, устаревшие данные на диске всё равно будут генерировать неверную контрольную сумму и будут обнаружены с высокой вероятностью. Для повышения производительности контрольная сумма использует только разреженный образец 32-битовых слов из записи данных — исследования дизайна во время планирования этапов SQLite 3.0.0 показали значительный спад производительности при контрольной сумме всей страницы.

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

Если M равно -1 в исходном заголовке журнала, то количество записей о страницах, которые следуют за ним, вычисляется путём вычисления количества записей о страницах, которые поместятся в доступном пространстве оставшейся части файла журнала.

4. Журнал предзаписи

Начиная с версии 3.7.0 (2010-07-21), SQLite поддерживает новую механику управления транзакциями, называемую «журнал предзаписи» или «ЖП». Когда база данных находится в режиме ЖП, все подключения к этой базе данных должны использовать ЖП. Определённая база данных будет использовать либо журнал отката, либо ЖП, но не оба одновременно. Журнал предзаписи всегда находится в той же директории, что и файл базы данных, и имеет то же имя, что и файл базы данных, но со строкой "-wal" в конце.

4.1. Формат файла ЖП

Файл ЖП состоит из заголовка, за которым следуют ноль или более «кадров». Каждый кадр записывает переработанное содержимое отдельной страницы из файла базы данных. Все изменения в базе данных регистрируются путём записи кадров в ЖП. Транзакции завершаются, когда записывается кадр, содержащий маркер завершения. Один ЖП может и обычно записывает несколько транзакций. Периодически содержимое ЖП передаётся обратно в файл базы данных в операции, называемой «контрольной точкой».

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

Заголовок ЖП имеет размер 32 байта и состоит из следующих восьми значений беззнаковых 32-битных целых чисел в формате big-endian:

Формат заголовка ЖП
Смещение Размер Описание
0 4 Магическое число. 0x377f0682 или 0x377f0683
4 4 Версия формата файла. В настоящее время 3007000.
8 4 Размер страницы базы данных. Пример: 1024
12 4 Номер последовательности контрольной точки
16 4 Соль-1: случайное целое число, увеличиваемое с каждой контрольной точкой
20 4 Соль-2: другое случайное число для каждой контрольной точки
24 4 Контрольная сумма-1: Первая часть контрольной суммы первых 24 байтов заголовка
28 4 Контрольная сумма-2: Вторая часть контрольной суммы первых 24 байтов заголовка

Непосредственно за заголовком WAL следуют нуль или более кадров. Каждый кадр состоит из 24-байтового заголовка кадра, за которым следуют размер_страницы байт данных страницы. Заголовок кадра содержит шесть 32-битных беззнаковых целых значений в формате big-endian, как следует из следующего:

Формат заголовка кадра WAL
Смещение Размер Описание
0 4 Номер страницы
4 4 Для записей фиксации, размер файла базы данных в страницах после фиксации. Для всех других записей — ноль.
8 4 Соль-1, скопированная из заголовка WAL
12 4 Соль-2, скопированная из заголовка WAL
16 4 Контрольная сумма-1: Кумулятивная контрольная сумма до и включая эту страницу
20 4 Контрольная сумма-2: Вторая половина кумулятивной контрольной суммы.

Кадр считается допустимым только в том случае, если следующие условия истинны:

  1. Значения соли-1 и соли-2 в заголовке кадра совпадают со значениями соли в заголовке wal

  2. Значения контрольных сумм в последних 8 байтах заголовка кадра точно совпадают с контрольной суммой, вычисленной последовательно для первых 24 байтов заголовка WAL и первых 8 байтов и содержимого всех кадров до и включая текущий кадр.

4.2. Алгоритм контрольной суммы

Контрольная сумма вычисляется путем интерпретации входных данных как четного числа беззнаковых 32-битных целых чисел: x(0) до x(N). 32-битные целые числа имеют формат big-endian, если магическое число в первых 4 байтах заголовка WAL равно 0x377f0683, и формат little-endian, если магическое число равно 0x377f0682. Значения контрольных сумм всегда хранятся в заголовке кадра в формате big-endian, независимо от порядка байтов, используемого для вычисления контрольной суммы.

Алгоритм контрольной суммы работает только для содержимого, длина которого кратна 8 байтам. Другими словами, если входными данными являются x(0) до x(N), то N должно быть нечетным. Алгоритм контрольной суммы следующий:

 
s0 = s1 = 0
for i from 0 to n-1 step 2:
   s0 += x(i) + s1;
   s1 += x(i+1) + s0;
endfor
# result in s0 and s1

Выходные значения s0 и s1 — это взвешенные контрольные суммы с использованием весов Фибоначчи в обратном порядке. (Наибольший вес Фибоначчи соответствует первому элементу суммируемой последовательности). Значение s1 охватывает все 32-битные целые члены последовательности, в то время как s0 опускает последний член.

4.3. Алгоритм контрольной точки

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

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

4.4. Сброс WAL

После полной контрольной точки, если ни один другой подключение не находится в транзакциях, использующих WAL, последующие транзакции записи могут перезаписывать файл WAL с начала. Это называется «сбросом WAL». В начале первой новой транзакции записи значение соли-1 в заголовке WAL увеличивается, а значение соли-2 случайным образом изменяется. Эти изменения солей делают недействительными старые кадры в WAL, которые уже были проверены, но еще не перезаписаны, и предотвращают их повторную проверку.

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

4.5. Алгоритм чтения

Для чтения страницы из базы данных (назовем ее номером страницы P) читатель сначала проверяет WAL, чтобы убедиться, что он содержит страницу P. Если это так, то последняя допустимая копия страницы P, за которой следует кадр фиксации или которая сама является кадром фиксации, становится значением для чтения. Если WAL не содержит копий страницы P, которые являются допустимыми и представляют собой кадр фиксации или за которым следует кадр фиксации, то страница P считывается из файла базы данных.

Для начала транзакции чтения читатель записывает количество кадров значений в WAL как «mxFrame». (Дополнительные сведения) Читатель использует это записанное значение mxFrame для всех последующих операций чтения. Новые транзакции могут быть добавлены в WAL, но до тех пор, пока читатель использует свое исходное значение mxFrame и игнорирует добавленное позже содержимое, читатель увидит согласованную моментальную фотографию базы данных из одной точки времени. Этот метод позволяет нескольким одновременным читателям просматривать различные версии содержимого базы данных одновременно.

Алгоритм чтения в предыдущих абзацах работает правильно, но поскольку кадры страницы P могут появляться где угодно в WAL, читателю необходимо просканировать весь WAL в поисках кадров страницы P. Если WAL большой (обычно несколько мегабайт), этот поиск может быть медленным, и производительность чтения страдает. Чтобы решить эту проблему, поддерживается отдельная структура данных под названием wal-индекс для ускорения поиска кадров определенной страницы.

4.6. Формат индекса WAL

По концепции, wal-индекс — это общая память, хотя текущие реализации VFS используют файловую карту памяти для обеспечения переносимости на разные операционные системы. Файл карты памяти находится в той же директории, что и база данных, и имеет то же имя, что и база данных, с добавленным суффиксом "-shm". Поскольку wal-индекс является общей памятью, SQLite не поддерживает journal_mode=WAL на файловой системе сети, когда клиенты находятся на разных машинах, так как все клиенты базы данных должны иметь возможность совместно использовать одну и ту же память.

Цель wal-индекса — быстро ответить на этот вопрос:

Учитывая номер страницы P и максимальный индекс кадра WAL M, вернуть наибольший индекс кадра WAL для страницы P, не превышающий M, или вернуть NULL, если нет кадров для страницы P, не превышающих M.

Значение M в предыдущем абзаце — это значение «mxFrame», определенное в разделе 4.4, которое считывается в начале транзакции и которое определяет максимальный кадр из WAL, который будет использовать читатель.

wal-индекс является временным. После сбоя wal-индекс восстанавливается из исходного файла WAL. VFS должен либо обрезать, либо обнулить заголовок wal-индекса при закрытии последнего подключения к нему. Поскольку wal-индекс является временным, он может использовать архитектурно-специфический формат; он не обязан быть кроссплатформенным. Следовательно, в отличие от форматов базы данных и файла WAL, которые хранят все значения в формате big-endian, wal-индекс хранит многобайтовые значения в родном порядке байтов хост-компьютера.

Этот документ касается постоянного состояния файла базы данных, и поскольку wal-индекс — это временная структура, здесь не будет предоставлена дополнительная информация о формате wal-индекса. Дополнительные сведения о формате wal-индекса содержатся в отдельном документе Формат файла индекса WAL.

Эта страница была в последний раз изменена 14 ноября 2024 г. в 16:04:37 по UTC

SQLite is in the Public Domain.
https://sqlite.org/fileformat.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API