Формат файла базы данных
Содержание
В этом документе описан и определён формат файлов базы данных на диске, используемый всеми выпусками SQLite начиная с версии 3.0.0 (2004-06-18).
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 байт файла базы данных составляют заголовок файла базы данных. Заголовок файла базы данных разделён на поля, показанные в таблице ниже. Все многобайтовые поля заголовка файла базы данных хранятся начиная со старшего байта (порядок байтов от старшего к младшему).
Формат заголовка базы данных| Смещение | Размер | Описание |
|---|---|---|
| 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 | Cookie схемы. |
| 44 | 4 | Номер формата схемы. Поддерживаются форматы схемы 1, 2, 3 и 4. |
| 48 | 4 | Размер кэша страниц по умолчанию. |
| 52 | 4 | Номер страницы наибольшей корневой страницы B-дерева в режимах auto-vacuum или incremental-vacuum либо ноль в противном случае. |
| 56 | 4 | Кодировка текста базы данных. Значение 1 означает UTF-8. Значение 2 означает UTF-16le. Значение 3 означает UTF-16be. |
| 60 | 4 | «Версия пользователя», считываемая и задаваемая с помощью прагмы user_version. |
| 64 | 4 | Истина (ненулевое значение) для режима incremental-vacuum. Ложь (нулевое значение) в противном случае. |
| 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) и более ранних это значение интерпретируется как целое число в порядке от старшего байта к младшему и должно быть степенью двойки в диапазоне от 512 до 32768 включительно. Начиная с версии 3.7.1 SQLite (2010-08-23) поддерживается размер страницы 65536 байт. Значение 65536 не помещается в двухбайтовое целое число, поэтому для указания размера страницы 65536 байт в смещении 16 используется значение 0x00 0x01. Это значение можно интерпретировать как единицу в порядке от старшего байта к младшему и считать магическим числом, обозначающим размер страницы 65536. Также можно рассматривать двухбайтовое поле как число в порядке от младшего байта к старшему и считать, что оно задаёт размер страницы, делённый на 256. Эти две интерпретации поля размера страницы эквивалентны.
1.3.3. Номера версий формата файла
Версии формата файла для записи и чтения, расположенные в смещениях 18 и 19, предназначены для поддержки усовершенствований формата файла в будущих версиях SQLite. В текущих версиях SQLite оба этих значения равны 1 для режимов с журналом отката и 2 для режима ведения журнала WAL. Если версия SQLite, реализующая текущую спецификацию формата файла, обнаруживает файл базы данных, в котором версия для чтения равна 1 или 2, а версия для записи больше 2, такой файл базы данных необходимо считать доступным только для чтения. Если обнаружен файл базы данных с версией для чтения больше 2, его нельзя читать или изменять.
1.3.4. Зарезервированные байты на страницу
SQLite может выделять небольшое количество дополнительных байтов в конце каждой страницы для использования расширениями. Например, эти дополнительные байты используются расширением шифрования SQLite для хранения nonce и/или криптографической контрольной суммы, связанной с каждой страницей. Размер «зарезервированного пространства», записанный в 1-байтовом целом числе со смещением 20, обозначает количество байтов в конце каждой страницы, резервируемых для расширений. Обычно это значение равно 0. Оно может быть нечётным.
«Полезный размер» страницы базы данных равен размеру страницы, заданному 2-байтовым целым числом в смещении 16 заголовка, за вычетом размера «зарезервированного» пространства, записанного в 1-байтовом целом числе в смещении 20 заголовка. Полезный размер страницы может быть нечётным числом. Однако он не может быть меньше 480. Иными словами, если размер страницы равен 512, размер зарезервированного пространства не может превышать 32.
1.3.5. Доли полезной нагрузки
Значения максимальной и минимальной долей встроенной полезной нагрузки, а также доли полезной нагрузки для листьев должны быть равны 64, 32 и 32 соответственно. Изначально предполагалось, что эти значения будут настраиваемыми параметрами, позволяющими изменять формат хранения алгоритма B-дерева. Однако эта возможность не поддерживается, и планов добавлять её в будущем нет. Поэтому эти три байта всегда содержат указанные значения.
1.3.6. Счётчик изменений файла
Счётчик изменений файла — это 4-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 24. Оно увеличивается каждый раз, когда блокировка файла базы данных снимается после внесения изменений. Когда один и тот же файл базы данных читают два или более процессов, каждый из них может обнаруживать изменения, внесённые другими процессами, отслеживая счётчик изменений. Обычно процесс должен сбросить кэш страниц базы данных, если другой процесс изменил базу данных, поскольку кэш устарел. Для этого и предназначен счётчик изменений файла.
В режиме WAL изменения базы данных обнаруживаются с помощью индекса WAL, поэтому счётчик изменений не требуется. Следовательно, в режиме WAL счётчик изменений может увеличиваться не при каждой транзакции.
1.3.7. Размер базы данных в заголовке
4-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 28 от начала заголовка, хранит размер файла базы данных в страницах. Если размер данных в заголовке недействителен (см. следующий абзац), размер базы данных вычисляется по фактическому размеру файла базы данных. Старые версии SQLite игнорировали размер базы данных в заголовке и использовали только фактический размер файла. Более новые версии SQLite используют размер базы данных в заголовке, если он доступен, но при его недействительности возвращаются к фактическому размеру файла.
Размер базы данных в заголовке считается действительным, только если он не равен нулю и если 4-байтовый счётчик изменений в смещении 24 в точности совпадает с 4-байтовым номером актуальности версии в смещении 92. Размер базы данных в заголовке всегда действителен, если базу данных изменяли только недавние версии SQLite — версии 3.7.0 (2010-07-21) и более поздние. Если в базу данных записывает устаревшая версия SQLite, она не знает о необходимости обновлять размер базы данных в заголовке, поэтому это значение может быть неверным. Однако устаревшие версии SQLite также не изменяют номер актуальности версии в смещении 92, и он не будет совпадать со счётчиком изменений. Таким образом, недействительные значения размера базы данных в заголовке можно обнаружить (и игнорировать), проверив, совпадает ли счётчик изменений с номером актуальности версии.
1.3.8. Список свободных страниц
Неиспользуемые страницы файла базы данных хранятся в списке свободных страниц. 4-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 32, содержит номер первой страницы списка свободных страниц либо ноль, если список пуст. 4-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 36, содержит общее количество страниц в списке свободных страниц.
1.3.9. Cookie схемы
Cookie схемы — это 4-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 40. Оно увеличивается при каждом изменении схемы базы данных. Подготовленная инструкция компилируется для конкретной версии схемы базы данных. При изменении схемы инструкцию необходимо подготовить заново. При запуске подготовленная инструкция сначала проверяет cookie схемы, чтобы убедиться, что его значение совпадает со значением на момент подготовки инструкции. Если cookie схемы изменился, инструкция либо автоматически подготавливается заново и выполняется повторно, либо завершается с ошибкой SQLITE_SCHEMA.
1.3.10. Номер формата схемы
Номер формата схемы — это 4-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 44. Номер формата схемы похож на номера версий формата файла для чтения и записи в смещениях 18 и 19, но относится к высокоуровневому формату SQL, а не к низкоуровневому формату B-дерева. В настоящее время определены четыре номера формата схемы:
- Формат 1 поддерживается всеми версиями SQLite начиная с версии 3.0.0 (2004-06-18).
- Формат 2 позволяет строкам одной таблицы содержать разное количество столбцов для поддержки функциональности ALTER TABLE ... ADD COLUMN. Поддержка чтения и записи в формате 2 была добавлена в SQLite версии 3.1.3 2005-02-20.
- Формат 3 позволяет дополнительным столбцам, добавленным с помощью ALTER TABLE ... ADD COLUMN, иметь значения по умолчанию, отличные от NULL. Эта возможность была добавлена в SQLite версии 3.1.4 2005-03-11.
- Формат 4 заставляет SQLite учитывать ключевое слово DESC в объявлениях индексов. (В индексах форматов 1, 2 и 3 ключевое слово DESC игнорируется.) Формат 4 также добавляет два новых логических значения типа записи (серийные типы 8 и 9). Поддержка формата 4 была добавлена в SQLite 3.3.0 2006-01-10.
Новые файлы баз данных, созданные SQLite, по умолчанию используют формат 4. Параметр SQLITE_DBCONFIG_LEGACY_FILE_FORMAT интерфейса C sqlite3_db_config() позволяет настроить SQLite на создание новых файлов баз данных в формате 1. Формат по умолчанию можно изменить с 4 на 1, задав при компиляции SQLITE_DEFAULT_FILE_FORMAT=1.
Если база данных полностью пуста и не содержит схемы, номер формата схемы может быть равен нулю.
1.3.11. Рекомендуемый размер кэша
4-байтовое знаковое целое число в порядке от старшего байта к младшему, расположенное в смещении 48, задаёт рекомендуемый размер кэша в страницах для файла базы данных. Это значение является лишь рекомендацией, и SQLite не обязан ему следовать. В качестве рекомендуемого размера используется абсолютное значение целого числа. Рекомендуемый размер кэша можно задать с помощью прагмы default_cache_size.
1.3.12. Параметры инкрементальной очистки
Два 4-байтовых целых числа в порядке от старшего байта к младшему, расположенные в смещениях 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-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 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-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 60, представляет версию пользователя, задаваемую и считываемую с помощью прагмы user_version. SQLite не использует версию пользователя.
1.3.15. Идентификатор приложения
4-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 68, — это «идентификатор приложения». Его можно задать командой PRAGMA application_id, чтобы указать, что база данных принадлежит определённому приложению или связана с ним. Идентификатор приложения предназначен для файлов баз данных, используемых в качестве файлового формата приложения. Такие утилиты, как file(1), могут использовать идентификатор приложения, чтобы определять конкретный тип файла, а не просто сообщать «база данных SQLite3». Список назначенных идентификаторов приложений можно найти в файле magic.txt в репозитории исходного кода SQLite.
1.3.16. Номер версии библиотеки записи и номер актуальности версии
4-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 96, хранит значение SQLITE_VERSION_NUMBER библиотеки SQLite, которая последней изменила файл базы данных. 4-байтовое целое число в порядке от старшего байта к младшему, расположенное в смещении 92, содержит значение счётчика изменений на момент записи номера версии. Целое число в смещении 92 указывает, для какой транзакции актуален номер версии, и иногда называется «номером актуальности версии».
1.3.17. Область заголовка, зарезервированная для расширения
Все остальные байты заголовка файла базы данных зарезервированы для будущих расширений и должны быть установлены в ноль.
1.4. Страница блокировочных байтов
Страница блокировочных байтов — это единственная страница файла базы данных, содержащая байты со смещениями от 1073741824 до 1073742335 включительно. Файл базы данных размером не более 1073741824 байт не содержит страницы блокировочных байтов. Файл базы данных размером более 1073741824 байт содержит ровно одну такую страницу.
Страница блокировочных байтов выделена для использования реализацией VFS, специфичной для операционной системы, при реализации примитивов блокировки файла базы данных. SQLite не использует страницу блокировочных байтов. Ядро SQLite никогда не читает и не записывает эту страницу, хотя реализации VFS, специфичные для операционной системы, могут считывать или записывать байты на ней в соответствии с особенностями и потребностями базовой системы. Встроенные в SQLite реализации VFS для Unix и Win32 не записывают данные на страницу блокировочных байтов, однако сторонние реализации VFS для других операционных систем могут это делать.
Страница блокировочных байтов появилась из-за необходимости поддерживать Win95 — преобладавшую на момент разработки этого формата файлов операционную систему, которая поддерживала только обязательную блокировку файлов. Все известные нам современные операционные системы поддерживают рекомендательную блокировку файлов, поэтому страница блокировочных байтов больше не нужна, но она сохранена для обратной совместимости.
1.5. Список свободных страниц
Файл базы данных может содержать одну или несколько страниц, которые в данный момент не используются. Например, неиспользуемые страницы могут появиться после удаления информации из базы данных. Они хранятся в списке свободных страниц и повторно используются, когда требуются дополнительные страницы.
Список свободных страниц организован как связный список узловых страниц, каждая из которых содержит номера нуля или более листовых страниц списка свободных страниц.
Узловая страница списка свободных страниц представляет собой массив 4-байтовых целых чисел в порядке от старшего байта к младшему. Размер массива равен максимальному числу целых чисел, помещающихся в полезную область страницы. Минимальный полезный размер составляет 480 байт, поэтому массив всегда содержит не менее 120 элементов. Первое целое число узловой страницы содержит номер следующей узловой страницы списка либо ноль, если это последняя узловая страница. Второе целое число узловой страницы содержит количество следующих за ним указателей на листовые страницы. Обозначим второе целое число узловой страницы буквой L. Если L больше нуля, то целые числа с индексами массива от 2 до L+1 включительно содержат номера листовых страниц списка свободных страниц.
Листовые страницы списка свободных страниц не содержат данных. Чтобы сократить операции ввода-вывода на диск, SQLite избегает чтения и записи таких страниц.
Ошибка в версиях SQLite до 3.6.0 (2008-07-16) приводила к тому, что база данных считалась повреждённой, если любое из последних 6 значений массива узловой страницы списка свободных страниц было ненулевым. В более новых версиях SQLite этой проблемы нет. Однако новые версии SQLite по-прежнему не используют последние шесть элементов массива узловой страницы, чтобы файлы баз данных, созданные новыми версиями SQLite, можно было читать старыми версиями SQLite.
Количество страниц списка свободных страниц хранится в заголовке базы данных как 4-байтовое целое число в порядке от старшего байта к младшему, расположенное со смещением 36 от начала файла. В заголовке базы данных также хранится номер первой узловой страницы списка свободных страниц — 4-байтовое целое число в порядке от старшего байта к младшему, расположенное со смещением 32 от начала файла.
1.6. Страницы B-дерева
Алгоритм B-дерева обеспечивает хранение ключей и данных с уникальными упорядоченными ключами на устройствах хранения, организованных по страницам. Справочную информацию о B-деревьях см. в книге Кнута Искусство программирования, том 3 «Сортировка и поиск», страницы 471–479. В SQLite используются два варианта B-деревьев. «B-деревья таблиц» используют 64-битный знаковый целочисленный ключ и хранят все данные в листьях. «B-деревья индексов» используют произвольные ключи и вообще не хранят данные.
Страница B-дерева бывает внутренней или листовой. Листовая страница содержит ключи; в случае B-дерева таблицы с каждым ключом связаны данные. Внутренняя страница содержит K ключей и K+1 указателей на дочерние страницы B-дерева. «Указатель» на внутренней странице B-дерева — это просто 32-битный беззнаковый номер дочерней страницы.
Количество ключей K на внутренней странице B-дерева почти всегда не меньше 2 и обычно значительно больше 2. Единственное исключение — внутренняя страница B-дерева с номером 1. На странице 1 доступно на 100 байт меньше места для хранения из-за заголовка базы данных в начале этой страницы, поэтому иногда (хотя и редко) внутренняя страница B-дерева 1 может содержать всего один ключ. Во всех остальных случаях K не меньше 2. Верхняя граница K определяется числом ключей, которое помещается на странице. Большие ключи в B-деревьях индексов разбиваются на страницы переполнения, чтобы ни один ключ не занимал более четверти доступного места на странице и, следовательно, каждая внутренняя страница могла хранить не менее 4 ключей. Целочисленные ключи B-деревьев таблиц никогда не бывают достаточно большими, чтобы требовалось переполнение, поэтому переполнение ключей встречается только в B-деревьях индексов.
Глубина листового B-дерева равна 1, а глубина любого внутреннего B-дерева на единицу больше максимальной глубины любого из его потомков. В корректно сформированной базе данных все потомки внутреннего B-дерева имеют одинаковую глубину.
На внутренней странице B-дерева указатели и ключи логически чередуются, причём с обоих концов находятся указатели. (Предыдущее предложение следует понимать концептуально: фактическое расположение ключей и указателей на странице сложнее и будет описано далее.) Все ключи на одной странице уникальны и логически упорядочены по возрастанию слева направо. (И здесь порядок является логическим, а не физическим. Фактическое расположение ключей на странице произвольно.) Для любого ключа X указатели слева от X ведут на страницы B-дерева, все ключи на которых меньше или равны X. Указатели справа от X ведут на страницы, все ключи на которых больше X.
На внутренней странице B-дерева каждый ключ вместе с непосредственно предшествующим ему указателем объединён в структуру, называемую «ячейкой». Самый правый указатель хранится отдельно. У листовой страницы B-дерева нет указателей, но в ней также используется структура ячейки для хранения ключей B-деревьев индексов либо ключей и содержимого B-деревьев таблиц. Данные также хранятся в ячейке.
У каждой страницы B-дерева может быть не более одной родительской страницы B-дерева. Страница B-дерева без родителя называется корневой страницей. Корневая страница B-дерева вместе со всеми её потомками образует полное B-дерево. Полное B-дерево может состоять из одной страницы, которая одновременно является листовой и корневой; на практике это довольно распространённый случай. Поскольку указатели ведут от родителей к потомкам, любую страницу полного B-дерева можно найти, зная только номер корневой страницы. Поэтому B-деревья идентифицируются по номеру их корневой страницы.
Страница B-дерева может быть страницей B-дерева таблицы или страницей B-дерева индекса. Все страницы в каждом полном B-дереве имеют один и тот же тип: таблица или индекс. В файле базы данных для каждой таблицы с rowid в схеме базы данных имеется одно B-дерево таблицы, включая системные таблицы, такие как sqlite_schema. В файле базы данных для каждого индекса в схеме имеется одно B-дерево индекса, включая неявные индексы, созданные ограничениями уникальности. С виртуальными таблицами не связаны B-деревья. Конкретные реализации виртуальных таблиц могут использовать теневые таблицы для хранения данных, однако эти теневые таблицы будут иметь отдельные записи в схеме базы данных. В таблицах WITHOUT ROWID используются B-деревья индексов, а не B-деревья таблиц, поэтому в файле базы данных для каждой таблицы WITHOUT ROWID имеется одно B-дерево индекса. B-дерево, соответствующее таблице sqlite_schema, всегда является B-деревом таблицы и всегда имеет корневую страницу 1. Таблица sqlite_schema содержит номер корневой страницы для каждой другой таблицы и индекса в файле базы данных.
Каждая запись в B-дереве таблицы состоит из 64-разрядного целочисленного ключа со знаком и произвольных данных размером не более 2147483647 байт. (Ключ B-дерева таблицы соответствует rowid таблицы SQL, реализуемой этим B-деревом.) Внутренние страницы B-дерева таблицы содержат только ключи и указатели на дочерние страницы. Все данные находятся в листовых страницах B-дерева таблицы.
Каждая запись в B-дереве индекса состоит из произвольного ключа размером не более 2147483647 байт и не содержит данных.
Назовём «полезной нагрузкой» ячейки её секцию произвольной длины. Для B-дерева индекса ключ всегда имеет произвольную длину, поэтому полезной нагрузкой является ключ. В ячейках внутренних страниц B-дерева таблицы нет элементов произвольной длины, поэтому такие ячейки не содержат полезной нагрузки. Листовые страницы B-дерева таблицы содержат данные произвольной длины, поэтому полезной нагрузкой ячеек на этих страницах являются данные.
Если размер полезной нагрузки ячейки превышает определённый порог (который будет задан далее), на странице B-дерева хранится только несколько первых байтов полезной нагрузки, а остальная часть хранится в связанном списке страниц переполнения.
Страница B-дерева разделена на области, расположенные в следующем порядке:
- 100-байтовый заголовок файла базы данных (находится только на странице 1)
- Заголовок страницы B-дерева размером 8 или 12 байт
- Массив указателей на ячейки
- Нераспределённое пространство
- Область содержимого ячеек
- Зарезервированная область
100-байтовый заголовок файла базы данных находится только на странице 1, которая всегда является страницей B-дерева таблицы. На всех остальных страницах B-дерева в файле базы данных этот 100-байтовый заголовок отсутствует.
Зарезервированная область — это область неиспользуемого пространства в конце каждой страницы (кроме страницы блокировки), которую расширения могут использовать для хранения информации для каждой страницы. Размер зарезервированной области определяется беззнаковым однобайтовым целым числом, расположенным по смещению 20 в заголовке файла базы данных. Обычно размер зарезервированной области равен нулю.
Размер заголовка страницы B-дерева составляет 8 байт для листовых страниц и 12 байт для внутренних страниц. Все многобайтовые значения в заголовке страницы записываются в порядке от старшего байта к младшему. Заголовок страницы B-дерева состоит из следующих полей:
Формат заголовка страницы B-дерева| Смещение | Размер | Описание |
|---|---|---|
| 0 | 1 | Однобайтовый флаг по смещению 0 указывает тип страницы 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 байта свободного блока — это целое число в порядке от старшего байта к младшему, задающее смещение следующего свободного блока в цепочке на странице B-дерева или равное нулю, если свободный блок является последним в цепочке. Третий и четвёртый байты каждого свободного блока образуют целое число в порядке от старшего байта к младшему, задающее размер свободного блока в байтах, включая 4-байтовый заголовок. Свободные блоки всегда связаны в порядке возрастания смещений. Второе поле заголовка страницы B-дерева содержит смещение первого свободного блока или равно нулю, если на странице нет свободных блоков. На корректно сформированной странице B-дерева перед первым свободным блоком всегда будет находиться хотя бы одна ячейка.
Для свободного блока требуется не менее 4 байт. Если в области содержимого ячеек есть отдельная группа из 1, 2 или 3 неиспользуемых байтов, эти байты образуют фрагмент. Общее количество байтов во всех фрагментах хранится в пятом поле заголовка страницы B-дерева. На корректно сформированной странице B-дерева общее количество байтов во фрагментах не может превышать 60.
Общий объём свободного пространства на странице B-дерева складывается из размера нераспределённой области, суммарного размера всех свободных блоков и количества фрагментированных свободных байтов. Время от времени SQLite может реорганизовать страницу B-дерева так, чтобы на ней не осталось свободных блоков или фрагментированных байтов, все неиспользуемые байты находились в области нераспределённого пространства, а все ячейки были плотно упакованы в конце страницы. Это называется «дефрагментацией» страницы B-дерева.
Целое число переменной длины, или «varint», — это статическое кодирование целых чисел в дополнительном коде с помощью кодирования Хаффмана, использующее меньше места для небольших положительных значений. Длина varint составляет от 1 до 9 байт. Varint состоит либо из нуля или более байтов с установленным старшим битом, за которыми следует один байт со сброшенным старшим битом, либо из девяти байтов — в зависимости от того, какой вариант короче. Для восстановления 64-разрядного целого числа в дополнительном коде используются семь младших битов каждого из первых восьми байтов и все 8 битов девятого байта. Varint записываются в порядке от старшего байта к младшему: биты из более раннего байта varint имеют больший вес, чем биты из последующих байтов.
Формат ячейки зависит от типа страницы B-дерева, на которой она находится. В следующей таблице перечислены элементы ячейки в порядке их появления для различных типов страниц B-дерева.
Листовая ячейка B-дерева таблицы (заголовок 0x0d):
-
- Varint, задающий общее количество байтов полезной нагрузки, включая данные, вынесенные на страницы переполнения
- Varint, задающий целочисленный ключ, также называемый «rowid»
- Начальная часть полезной нагрузки, которая не вынесена на страницы переполнения.
- Четырёхбайтовый целочисленный номер страницы в порядке от старшего байта к младшему для первой страницы списка страниц переполнения — отсутствует, если полезная нагрузка целиком помещается на странице B-дерева.
Внутренняя ячейка B-дерева таблицы (заголовок 0x05):
-
- Четырёхбайтовый номер страницы в порядке от старшего байта к младшему, являющийся указателем на левую дочернюю страницу.
- Varint, задающий целочисленный ключ
Листовая ячейка B-дерева индекса (заголовок 0x0a):
-
- Varint, задающий общее количество байтов полезной нагрузки ключа, включая данные, вынесенные на страницы переполнения
- Начальная часть полезной нагрузки, которая не вынесена на страницы переполнения.
- Четырёхбайтовый целочисленный номер страницы в порядке от старшего байта к младшему для первой страницы списка страниц переполнения — отсутствует, если полезная нагрузка целиком помещается на странице B-дерева.
Внутренняя ячейка B-дерева индекса (заголовок 0x02):
-
- Четырёхбайтовый номер страницы в порядке от старшего байта к младшему, являющийся указателем на левую дочернюю страницу.
- Varint, задающий общее количество байтов полезной нагрузки ключа, включая данные, вынесенные на страницы переполнения
- Начальная часть полезной нагрузки, которая не вынесена на страницы переполнения.
- Четырёхбайтовый целочисленный номер страницы в порядке от старшего байта к младшему для первой страницы списка страниц переполнения — отсутствует, если полезная нагрузка целиком помещается на странице B-дерева.
Приведённую выше информацию можно представить в виде таблицы:
Формат ячейки B-дерева| Тип данных | Где встречается... | Описание | |||
|---|---|---|---|---|---|
| Листовая таблица (0x0d) | Внутренняя таблица (0x05) | Листовой индекс (0x0a) | Внутренний индекс (0x02) | ||
| 4-байтовое целое число | ✔ | ✔ | Номер страницы левой дочерней страницы | ||
| varint | ✔ | ✔ | ✔ | Количество байтов полезной нагрузки | |
| varint | ✔ | ✔ | Rowid | ||
| Массив байтов | ✔ | ✔ | ✔ | Полезная нагрузка | |
| 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-дерева, её остаток переносится на страницы переполнения. Страницы переполнения образуют связанный список. Первые четыре байта каждой страницы переполнения — это целое число в порядке от старшего байта к младшему, задающее номер следующей страницы в цепочке или равное нулю для последней страницы цепочки. Байты с пятого по последний используемый байт применяются для хранения содержимого переполнения.
1.8. Страницы карты указателей (ptrmap)
Страницы карты указателей, или ptrmap, — это дополнительные страницы, вставляемые в базу данных для повышения эффективности режимов auto_vacuum и incremental_vacuum. Обычно другие типы страниц базы данных содержат указатели от родительских страниц к дочерним. Например, внутренняя страница B-дерева содержит указатели на дочерние страницы B-дерева, а в цепочке переполнения указатель связывает предыдущий элемент со следующим. Страница ptrmap содержит информацию о связях в обратном направлении — от дочерней страницы к родительской.
Страницы ptrmap должны присутствовать в любом файле базы данных, в котором значение наибольшей корневой страницы B-дерева по смещению 52 в заголовке базы данных не равно нулю. Если значение наибольшей корневой страницы B-дерева равно нулю, база данных не должна содержать страниц ptrmap.
В базе данных со страницами ptrmap первая страница ptrmap — это страница 2. Страница ptrmap состоит из массива пятибайтовых записей. Пусть J — количество пятибайтовых записей, помещающихся в используемую область страницы. (Иными словами, J=U/5.) Первая страница ptrmap содержит информацию об обратных указателях для страниц с 3 по J+2 включительно. Вторая страница карты указателей находится на странице J+3 и содержит информацию об обратных указателях для страниц с J+4 по 2*J+3 включительно. И так далее для всего файла базы данных.
В базе данных, использующей страницы ptrmap, все страницы в местах, определяемых вычислением из предыдущего абзаца, должны быть страницами ptrmap, и никакие другие страницы не могут быть страницами ptrmap. Исключение: если страница блокировки байтов совпадает по номеру со страницей ptrmap, то в этом единственном случае ptrmap переносится на следующую страницу.
Каждая пятибайтовая запись на странице ptrmap содержит информацию об обратной связи для одной из страниц, непосредственно следующих за картой указателей. Если страница B является страницей ptrmap, информация об обратной связи для страницы B+1 находится в первой записи карты указателей. Информация о странице B+2 находится во второй записи и так далее.
Каждая пятибайтовая запись ptrmap состоит из одного байта с информацией о «типе страницы», за которым следует четырёхбайтовый номер страницы в порядке от старшего байта к младшему. Распознаются пять типов страниц:
- Корневая страница B-дерева. Номер страницы должен быть равен нулю.
- Страница списка свободных страниц. Номер страницы должен быть равен нулю.
- Первая страница цепочки переполнения полезной нагрузки ячейки. Номер страницы указывает на страницу B-дерева, содержащую ячейку с переполненным содержимым.
- Страница цепочки переполнения, не являющаяся первой. Номер страницы указывает на предыдущую страницу цепочки переполнения.
- Некорневая страница B-дерева. Номер страницы указывает на родительскую страницу B-дерева.
В любом файле базы данных, содержащем страницы ptrmap, все корневые страницы B-деревьев должны находиться перед любыми некорневыми страницами B-деревьев, страницами переполнения полезной нагрузки ячеек или страницами списка свободных страниц. Это ограничение гарантирует, что корневая страница никогда не будет перемещена во время выполнения auto-vacuum или incremental-vacuum. Логика auto-vacuum не умеет обновлять поле root_page таблицы sqlite_schema, поэтому для сохранения целостности таблицы sqlite_schema необходимо не допускать перемещения корневых страниц во время auto-vacuum. Корневые страницы перемещаются в начало файла базы данных операциями CREATE TABLE, CREATE INDEX, DROP TABLE и DROP INDEX.
2. Уровень схемы
В предыдущем тексте описаны низкоуровневые аспекты формата файла SQLite. Механизм B-дерева предоставляет мощные и эффективные средства доступа к большим наборам данных. В этом разделе описывается использование низкоуровневого уровня B-деревьев для реализации возможностей SQL более высокого уровня.
2.1. Формат записи
Выше данные листовой страницы B-дерева таблицы и ключ страницы B-дерева индекса были охарактеризованы как произвольные последовательности байтов. В предыдущем обсуждении упоминалось, что один ключ меньше другого, но не определялось, что означает «меньше». В этом разделе рассматриваются эти вопросы.
Полезная нагрузка — будь то данные B-дерева таблицы или ключи B-дерева индекса — всегда имеет «формат записи». Формат записи определяет последовательность значений, соответствующих столбцам таблицы или индекса. Формат записи задаёт количество столбцов, тип данных каждого столбца и содержимое каждого столбца.
В формате записи широко используется описанное выше представление 64-разрядных целых чисел со знаком в виде целых чисел переменной длины, или varint.
Запись состоит из заголовка и тела, расположенных именно в таком порядке. Заголовок начинается с одного varint, задающего общее количество байтов в заголовке. Значение varint — это размер заголовка в байтах, включая размер самого varint. За varint размера следуют один или несколько дополнительных varint — по одному на каждый столбец. Эти дополнительные varint называются номерами «серийного типа» и определяют тип данных каждого столбца согласно следующей таблице:
Коды серийных типов формата записи| Серийный тип | Размер содержимого | Значение |
|---|---|---|
| 0 | 0 | Значение NULL. |
| 1 | 1 | Значение — 8-разрядное целое число в дополнительном коде. |
| 2 | 2 | Значение — 16-разрядное целое число в дополнительном коде, записанное в порядке от старшего байта к младшему. |
| 3 | 3 | Значение — 24-разрядное целое число в дополнительном коде, записанное в порядке от старшего байта к младшему. |
| 4 | 4 | Значение — 32-разрядное целое число в дополнительном коде, записанное в порядке от старшего байта к младшему. |
| 5 | 6 | Значение — 48-разрядное целое число в дополнительном коде, записанное в порядке от старшего байта к младшему. |
| 6 | 8 | Значение — 64-разрядное целое число в дополнительном коде, записанное в порядке от старшего байта к младшему. |
| 7 | 8 | Значение — 64-разрядное число с плавающей точкой стандарта IEEE 754-2008, записанное в порядке от старшего байта к младшему. |
| 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 очень эффективен для кодирования заголовка записи.
Значения каждого столбца в записи непосредственно следуют за заголовком. Для серийных типов 0, 8, 9, 12 и 13 длина значения равна нулю байт. Если все столбцы имеют такие типы, раздел тела записи пуст.
В записи может быть меньше значений, чем столбцов в соответствующей таблице. Такое может произойти, например, после выполнения инструкции SQL ALTER TABLE ... ADD COLUMN, которая увеличила число столбцов в схеме таблицы, не изменив существующие строки. Отсутствующие в конце записи значения заполняются с использованием значения по умолчанию, определённого для соответствующих столбцов в схеме таблицы.
2.2. Порядок сортировки записей
Порядок ключей в B-дереве индекса определяется порядком сортировки записей, которым соответствуют эти ключи. Сравнение записей выполняется по столбцам. Столбцы записи проверяются слева направо. Относительный порядок двух записей определяется первой парой неравных столбцов. Порядок сортировки отдельных столбцов следующий:
- Значения NULL (серийный тип 0) сортируются первыми.
- Числовые значения (серийные типы с 1 по 9) сортируются после NULL в числовом порядке.
- Текстовые значения (нечётные серийные типы 13 и выше) сортируются после числовых значений в порядке, определяемом функцией сопоставления столбцов.
- Значения BLOB (чётные серийные типы 12 и выше) сортируются последними в порядке, определяемом функцией memcmp().
Для вычисления порядка текстовых полей необходима функция сопоставления для каждого столбца. В 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 в определении столбца. При индексировании столбца по умолчанию для него в индексе используется та же функция сопоставления, что и в инструкции 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. При обращении к столбцу INTEGER PRIMARY KEY SQLite всегда использует ключ B-дерева таблицы, а не значение NULL.
Если аффинность столбца — 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. Исключение избыточных столбцов из PRIMARY KEY таблиц WITHOUT ROWID
Если в PRIMARY KEY таблицы WITHOUT ROWID один и тот же столбец используется несколько раз с одинаковой последовательностью сопоставления, второе и последующие вхождения этого столбца в определении PRIMARY KEY игнорируются. Например, следующие инструкции 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;
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);
В предельном случае, когда индексируемые столбцы охватывают все столбцы 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
Страница 1 файла базы данных является корневой страницей 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 и увеличивающимся на единицу для каждого ограничения, указанного в определении таблицы. В таблице 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" иногда также называют одним из следующих псевдонимов:
- sqlite_master
- sqlite_temp_schema
- sqlite_temp_master
Поскольку имя таблицы схемы нигде в формате файла не встречается, смысл файла базы данных не меняется, если приложение обращается к таблице схемы, используя одно из этих альтернативных имён.
2.6.2. Внутренние объекты схемы
Помимо таблиц, индексов, представлений и триггеров, созданных приложением и/или разработчиком с помощью операторов CREATE SQL, таблица 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 автоматически удаляется при удалении таблицы. Если при обновлении таблицы AUTOINCREMENT соответствующая строка sqlite_sequence отсутствует, создаётся новая строка sqlite_sequence. Если значение sqlite_sequence.seq для таблицы 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.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 индекса запрещает использовать этот индекс с оптимизацией пропуска сканирования.
В будущих усовершенствованиях SQLite в конец столбца 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 до 3.6.18 или после 3.7.8 не читает и не записывает таблицу sqlite_stat2. Таблица sqlite_stat2 содержит дополнительные сведения о распределении ключей в индексе. Схема таблицы 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, и таблица sqlite_stat2, если она существует, просто игнорируется.
2.6.6. Таблица sqlite_stat3
Таблица sqlite_stat3 используется только в том случае, если SQLite скомпилирована с SQLITE_ENABLE_STAT3 или SQLITE_ENABLE_STAT4, а номер версии SQLite равен 3.7.9 (2011-11-01) или выше. Ни одна версия SQLite до 3.7.9 не читает и не записывает таблицу sqlite_stat3. Если используется параметр компиляции 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 должна иметь меньший rowid, чем выборка S2.
2.6.7. Таблица sqlite_stat4
Таблица sqlite_stat4 создаётся и используется только в том случае, если SQLite скомпилирована с SQLITE_ENABLE_STAT4, а номер версии SQLite равен 3.8.1 (2013-10-17) или выше. Ни одна версия SQLite до 3.8.1 не читает и не записывает таблицу sqlite_stat4. Таблица sqlite_stat4 содержит дополнительные сведения о распределении ключей в индексе или распределении ключей первичного ключа таблицы WITHOUT ROWID. Иногда планировщик запросов может использовать дополнительные сведения из таблицы sqlite_stat4 для разработки более эффективных и быстрых алгоритмов выполнения запросов. Схема таблицы sqlite_stat4 имеет следующий вид:
CREATE TABLE sqlite_stat4(tbl,idx,nEq,nLt,nDLt,sample);
Для каждого индекса, по которому имеются статистические данные, в таблице sqlite_stat4 обычно содержится от 10 до 40 записей, однако эти значения не являются строгими ограничениями. Значения столбцов таблицы sqlite_stat4 описаны ниже:
| 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 содержит только столбцы первичного ключа. Пусть N — количество столбцов, закодированных в BLOB sqlite_stat4.sample. Для индексов обычной таблицы 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". Для одной базы данных может существовать только один журнал отката, поэтому одновременно для одной базы данных может быть открыта только одна транзакция записи.
Перед изменением любой страницы базы данных, содержащей данные, её исходное неизменённое содержимое записывается в журнал отката. Если транзакция прерывается и её необходимо откатить, журнал отката можно использовать для восстановления исходного состояния базы данных. Листовые страницы списка свободных страниц не содержат данных, которые требовалось бы восстановить при откате, поэтому перед изменением они не записываются в журнал, что позволяет сократить объём операций ввода-вывода с диском.
Если транзакция прервана из-за сбоя приложения, отдельного процесса, операционной системы или оборудования, отключения питания либо аппаратного сбоя, основной файл базы данных может остаться в несогласованном состоянии. При следующей попытке SQLite открыть файл базы данных будет обнаружен файл журнала отката, и журнал будет автоматически применён для восстановления состояния базы данных на момент начала незавершённой транзакции.
Журнал отката считается действительным, только если он существует и содержит корректный заголовок. Поэтому зафиксировать транзакцию можно одним из трёх способов:
- Удалить файл журнала отката;
- Обрезать файл журнала отката до нулевой длины; или
- Перезаписать заголовок журнала отката недопустимым текстом (например, одними нулями).
Эти три способа фиксации транзакции соответствуют настройкам DELETE, TRUNCATE и PERSIST директивы journal_mode соответственно.
Корректный журнал отката начинается с заголовка следующего формата:
Формат заголовка журнала отката| Смещение | Размер | Описание |
|---|---|---|
| 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-разрядное целое число, вычисляемое следующим образом:
- Инициализировать контрольную сумму значением nonce для контрольной суммы, находящимся в заголовке журнала по смещению 12.
- Инициализировать индекс X значением N-200 (где N — размер страницы базы данных в байтах).
- Интерпретировать байт страницы по смещению X как 8-разрядное беззнаковое целое число и прибавить его значение к контрольной сумме.
- Вычесть 200 из X.
- Если X больше или равно нулю, вернуться к шагу 3.
Контрольная сумма используется для обнаружения неполной записи страницы журнала в случае сбоя питания. При каждом начале транзакции используется новое случайное значение nonce, чтобы свести к минимуму вероятность того, что незаписанные секторы случайно содержат данные той же страницы, которая входила в предыдущие журналы. Изменение nonce для каждой транзакции позволяет с высокой вероятностью обнаружить устаревшие данные на диске, поскольку они дадут неверную контрольную сумму. По соображениям производительности контрольная сумма использует лишь разреженную выборку 32-разрядных слов записи данных: исследования, проведённые на этапе планирования SQLite 3.0.0, показали, что вычисление контрольной суммы для всей страницы заметно снижает производительность.
Пусть значение количества страниц по смещению 8 в заголовке журнала равно M. Если M больше нуля, после M записей страниц файл журнала может быть дополнен нулями до ближайшей границы, кратной размеру сектора, после чего может быть вставлен ещё один заголовок журнала. Все заголовки журналов в одном журнале должны содержать одинаковые размеры страниц базы данных и секторов.
Если M равно -1 в начальном заголовке журнала, количество следующих за ним записей страниц вычисляется исходя из числа записей страниц, помещающихся в доступное пространство оставшейся части файла журнала.
4. Журнал с опережающей записью
Начиная с версии 3.7.0 (2010-07-21), SQLite поддерживает новый механизм управления транзакциями, называемый «журналом с опережающей записью» или «WAL». Когда база данных работает в режиме WAL, все подключения к этой базе данных должны использовать WAL. Для конкретной базы данных используется либо журнал отката, либо WAL, но не оба одновременно. Файл WAL всегда находится в том же каталоге, что и файл базы данных, и имеет то же имя, что и файл базы данных, но с добавленной строкой "-wal".
4.1. Формат файла WAL
Файл WAL состоит из заголовка и нуля или более «кадров». Каждый кадр содержит изменённое содержимое одной страницы файла базы данных. Все изменения базы данных записываются в WAL в виде кадров. Транзакции фиксируются при записи кадра, содержащего маркер фиксации. Один файл WAL может и обычно действительно содержит несколько транзакций. Периодически содержимое WAL переносится обратно в файл базы данных в ходе операции, называемой «контрольной точкой».
Один файл WAL можно использовать повторно многократно. Иными словами, WAL может заполниться кадрами, после чего для него выполняется контрольная точка, а затем старые кадры перезаписываются новыми. WAL всегда растёт от начала к концу. Контрольные суммы и счётчики, связанные с каждым кадром, используются для определения того, какие кадры в WAL действительны, а какие остались от предыдущих контрольных точек.
Размер заголовка WAL составляет 32 байта. Он состоит из следующих восьми беззнаковых 32-разрядных целых чисел в формате с прямым порядком байтов:
Формат заголовка WAL| Смещение | Размер | Описание |
|---|---|---|
| 0 | 4 | Магическое число. 0x377f0682 или 0x377f0683 |
| 4 | 4 | Версия формата файла. В настоящее время 3007000. |
| 8 | 4 | Размер страницы базы данных. Пример: 1024 |
| 12 | 4 | Порядковый номер контрольной точки |
| 16 | 4 | Salt-1: случайное целое число, увеличиваемое при каждой контрольной точке |
| 20 | 4 | Salt-2: другое случайное число для каждой контрольной точки |
| 24 | 4 | Checksum-1: первая часть контрольной суммы для первых 24 байт заголовка |
| 28 | 4 | Checksum-2: вторая часть контрольной суммы для первых 24 байт заголовка |
Сразу после заголовка WAL располагается ноль или более кадров. Каждый кадр состоит из 24-байтового заголовка кадра, за которым следуют размер страницы байт данных страницы. Заголовок кадра состоит из шести беззнаковых 32-разрядных целых чисел в формате с прямым порядком байтов:
Формат заголовка кадра WAL| Смещение | Размер | Описание |
|---|---|---|
| 0 | 4 | Номер страницы |
| 4 | 4 | Для записей фиксации — размер файла базы данных после фиксации в страницах. Для всех остальных записей — ноль. |
| 8 | 4 | Salt-1, скопированное из заголовка WAL |
| 12 | 4 | Salt-2, скопированное из заголовка WAL |
| 16 | 4 | Checksum-1: накопительная контрольная сумма вплоть до этой страницы включительно |
| 20 | 4 | Checksum-2: вторая часть накопительной контрольной суммы. |
Кадр считается действительным тогда и только тогда, когда выполняются следующие условия:
Значения salt-1 и salt-2 в заголовке кадра совпадают со значениями salt в заголовке WAL
Значения контрольной суммы в последних 8 байтах заголовка кадра точно совпадают с контрольной суммой, вычисленной последовательно по первым 24 байтам заголовка WAL, первым 8 байтам и содержимому всех кадров вплоть до текущего включительно.
4.2. Алгоритм вычисления контрольной суммы
Контрольная сумма вычисляется путём интерпретации входных данных как чётного количества беззнаковых 32-разрядных целых чисел: x(0) — x(N). Если магическое число в первых 4 байтах заголовка WAL равно 0x377f0683, 32-разрядные целые числа записаны в формате с прямым порядком байтов; если магическое число равно 0x377f0682, они записаны в формате с обратным порядком байтов. Значения контрольной суммы всегда хранятся в заголовке кадра в формате с прямым порядком байтов, независимо от порядка байтов, используемого при вычислении контрольной суммы.
Алгоритм контрольной суммы работает только с данными, длина которых кратна 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». В начале первой новой транзакции записи значение salt-1 заголовка WAL увеличивается, а значение salt-2 устанавливается случайным образом. Эти изменения значений salt делают недействительными старые кадры в WAL, для которых уже была создана контрольная точка, но которые ещё не были перезаписаны, и предотвращают повторное создание для них контрольной точки.
При сбросе файл WAL можно усечь, но это необязательно. Обычно производительность немного выше, если WAL не усекается, поскольку файловые системы, как правило, перезаписывают существующий файл быстрее, чем увеличивают его размер.
4.5. Алгоритм чтения
Чтобы прочитать страницу из базы данных (назовём её страницей с номером P), читатель сначала проверяет, содержится ли страница P в WAL. Если да, то в качестве прочитанного значения используется последний корректный экземпляр страницы P, за которым следует кадр фиксации или который сам является кадром фиксации. Если WAL не содержит корректных копий страницы P, которые являются кадрами фиксации или за которыми следуют кадры фиксации, страница P считывается из файла базы данных.
Чтобы начать транзакцию чтения, читатель записывает количество кадров со значениями в WAL как «mxFrame». (Подробнее) Читатель использует это записанное значение mxFrame для всех последующих операций чтения. В WAL могут добавляться новые транзакции, но пока читатель использует исходное значение mxFrame и игнорирует содержимое, добавленное впоследствии, он будет видеть согласованный снимок базы данных на определённый момент времени. Этот метод позволяет нескольким параллельным читателям одновременно просматривать разные версии содержимого базы данных.
Алгоритм чтения, описанный в предыдущих абзацах, работает правильно, но, поскольку кадры для страницы P могут находиться в любом месте WAL, читателю приходится просматривать весь WAL в поисках кадров страницы P. Если WAL велик (обычно это несколько мегабайт), такое сканирование может быть медленным, что снижает производительность чтения. Для решения этой проблемы используется отдельная структура данных, называемая индексом WAL (wal-index), которая ускоряет поиск кадров определённой страницы.
4.6. Формат индекса WAL
Концептуально индекс WAL представляет собой разделяемую память, хотя текущие реализации VFS для переносимости между операционными системами используют файл, отображённый в память. Этот файл находится в том же каталоге, что и база данных, и имеет то же имя с добавленным суффиксом "-shm". Поскольку индекс WAL является разделяемой памятью, SQLite не поддерживает journal_mode=WAL в сетевой файловой системе, если клиенты находятся на разных компьютерах, поскольку все клиенты базы данных должны иметь возможность совместно использовать одну и ту же память.
Индекс WAL предназначен для быстрого ответа на следующий вопрос:
Для заданного номера страницы P и максимального индекса кадра WAL M вернуть наибольший индекс кадра WAL для страницы P, не превышающий M, либо вернуть NULL, если нет кадров для страницы P, индексы которых не превышают M.
Значение M из предыдущего абзаца — это значение «mxFrame», определённое в разделе 4.5, которое считывается в начале транзакции и задаёт максимальный кадр из WAL, используемый читателем.
Индекс WAL является временной структурой. После сбоя он восстанавливается из исходного файла WAL. VFS должна либо усечь заголовок индекса WAL, либо обнулить его при закрытии последнего подключения к нему. Поскольку индекс WAL является временной структурой, он может использовать формат, зависящий от архитектуры; он не обязан быть кроссплатформенным. Поэтому, в отличие от форматов базы данных и файла WAL, в которых все значения хранятся в порядке от старшего байта к младшему, в индексе WAL многобайтные значения хранятся в порядке байтов, принятом на компьютере-хосте.
Этот документ посвящён постоянному состоянию файла базы данных, а индекс WAL является временной структурой, поэтому здесь не приводится дополнительная информация о его формате. Дополнительные сведения о формате индекса WAL содержатся в отдельном документе Формат файла индекса WAL.
Эта страница в последний раз обновлялась 2025-12-25 10:33:36Z
SQLite is in the Public Domain.
https://sqlite.org/fileformat2.html