Формат файла в режиме WAL
Содержание
В этом документе описаны низкоуровневые подробности реализации режима WAL в Unix и Windows.
В отдельном документе «Формат файла» приведены сведения о структуре файла базы данных и файла журнала предварительной записи, используемого в режиме WAL. Однако подробности протокола блокировок и формата индекса WAL намеренно опущены, поскольку эти аспекты оставлены на усмотрение отдельных реализаций VFS. В этом документе приводятся недостающие сведения для VFS Unix и Windows.
Для полноты изложения некоторые сведения о форматировании верхнего уровня, содержащиеся в документе «Формат файла» и других источниках, здесь повторяются, если они относятся к обработке в режиме WAL.
1. Файлы на диске
При активном использовании базы данных в режиме WAL ее состояние описывается тремя отдельными файлами:
- Основной файл базы данных с произвольным именем «X».
- Файл журнала предварительной записи, обычно называемый «X-wal».
- Файл индекса WAL, обычно называемый «X-shm».
1.1. Основной файл базы данных
Формат основного файла базы данных описан в документе «Формат файла». Номера версий формата файла по смещениям 18 и 19 в основном файле базы данных должны оба иметь значение 2, указывающее на то, что база данных работает в режиме WAL. Основной файл базы данных может иметь любое имя, разрешенное базовой файловой системой. Специальные суффиксы файлов не требуются, хотя часто используются «.db», «.sqlite» и «.sqlite3».
1.2. Журнал предварительной записи или файл «-wal»
Файл журнала предварительной записи, или файл «wal», представляет собой журнал с воспроизведением вперед, в котором записываются транзакции, уже зафиксированные, но еще не примененные к основной базе данных. Подробности формата файла wal описаны в подразделе «Формат WAL» основного документа «Формат файла». Имя файла wal образуется добавлением четырех символов «-wal» в конец имени основного файла базы данных. В файловых системах 8+3 такие имена не допускаются, поэтому в этом случае суффикс файла заменяется на «.WAL». Однако файловые системы 8+3 становятся все более редкими, поэтому этим исключительным случаем обычно можно пренебречь.
1.3. Индекс WAL или файл «-shm»
Файл индекса WAL, или файл «shm», фактически не используется как файл. Вместо этого отдельные клиенты базы данных отображают файл shm в память с помощью mmap и используют его как общую память для координации доступа к базе данных, а также как кэш для быстрого поиска кадров в файле wal. Имя файла shm образуется добавлением четырех символов «-shm» к имени основного файла базы данных. В файловых системах 8+3 файл shm представляет собой основной файл базы данных с суффиксом «.SHM».
Файл shm не содержит данных базы данных и не требуется для восстановления базы данных после сбоя. Поэтому первый клиент, подключающийся к неактивной базе данных, обычно обрезает существующий файл shm. Поскольку содержимое файла shm не нужно сохранять после сбоя, файл shm никогда не синхронизируется с диском с помощью fsync(). Более того, если бы SQLite могла сообщить операционной системе, чтобы та никогда не сохраняла файл shm на диск, а всегда держала его в кэш-памяти, SQLite использовала бы такой механизм, чтобы избежать любых ненужных операций ввода-вывода, связанных с файлом shm. Однако в стандартном POSIX такого механизма нет.
Поскольку shm используется только для координации доступа между параллельными клиентами, при включенном режиме эксклюзивной блокировки файл shm не создается — это оптимизация. При включенном режиме эксклюзивной блокировки SQLite использует вместо файла shm, отображенного в память, память в куче.
1.4. Жизненный цикл файлов
При активном использовании базы данных в режиме WAL обычно существуют все три указанных выше файла. Исключение составляет файл индекса WAL, который не создается при включенном режиме эксклюзивной блокировки.
Если последний клиент базы данных корректно завершает работу, вызвав sqlite3_close(), автоматически выполняется контрольная точка, чтобы перенести всю информацию из файла wal в основную базу данных, а файлы shm и wal удаляются. Поэтому, когда базой данных не пользуется ни один клиент, на диске обычно остается только основной файл базы данных. Однако, если последний клиент не вызвал sqlite3_close() перед завершением работы или если последним отключился клиент, имеющий доступ только для чтения, операция окончательной очистки не выполняется, и файлы shm и wal могут остаться на диске даже тогда, когда база данных не используется.
1.5. Варианты
При включенном PRAGMA locking_mode=EXCLUSIVE (режиме эксклюзивной блокировки) одновременно открыть базу данных может только один клиент. Поскольку базой данных может пользоваться только один клиент, файл shm не создается. Вместо файла shm, отображенного в память, единственный клиент использует буфер в куче.
Если клиент с доступом для чтения и записи вызывает sqlite3_file_control(SQLITE_FCNTL_PERSIST_WAL) перед завершением работы, то при завершении работы контрольная точка все равно выполняется, но файлы shm и wal не удаляются. Это позволяет последующим клиентам, имеющим доступ только для чтения, подключаться к базе данных и читать ее.
2. Формат файла индекса WAL
Индекс WAL, или файл «shm», используется для координации доступа к базе данных нескольких клиентов, а также как кэш, помогающий клиентам быстро находить кадры в файле wal.
Поскольку файл shm не участвует в восстановлении, его порядок байтов не обязательно должен быть независимым от порядка байтов компьютера. Поэтому числовые значения в файле shm записываются в естественном порядке байтов компьютера, на котором он работает, а не преобразуются в определенный межплатформенный порядок байтов, как это делается для основного файла базы данных и файла wal.
Файл shm состоит из одной или нескольких хеш-таблиц размером 32768 байт каждая. При этом из начала самой первой хеш-таблицы выделен 136-байтовый заголовок, поэтому размер первой хеш-таблицы составляет всего 32632 байта. Общий размер файла shm всегда кратен 32768. В большинстве случаев общий размер файла shm составляет ровно 32768 байт. Файл shm должен увеличиваться более чем до размера одной хеш-таблицы только в том случае, если файл wal становится очень большим (более 4079 кадров). Поскольку стандартный порог автоматической контрольной точки равен 1000, файлы WAL редко достигают порога в 4079 кадров, необходимого для увеличения файла shm.
2.1. Заголовок индекса WAL
Первые 136 байт файла shm составляют заголовок. Заголовок shm состоит из трех основных частей:
Разделы заголовка индекса WAL| Байты | Описание |
|---|---|
| 0..47 | Первая копия сведений об индексе WAL |
| 48..95 | Вторая копия сведений об индексе WAL |
| 96..135 | Сведения о контрольной точке и блокировки |
Отдельные поля заголовка shm, за исключением значений соли, скопированных из заголовка WAL, являются беззнаковыми целыми числами в естественном порядке байтов компьютера. Значения соли в точности копируются из заголовка WAL и имеют порядок байтов, используемый в файле WAL. Размер целых чисел может составлять 8, 16, 32 или 64 бита. Ниже приведено подробное описание отдельных полей заголовка shm:
Подробное описание заголовка индекса WAL| Байты | Имя | Значение |
|---|---|---|
| 0..3 | iVersion | Номер версии формата индекса WAL. Всегда равен 3007000. |
| 4..7 | Неиспользуемое пространство-заполнитель. Должно быть равно нулю. | |
| 8..11 | iChange | Счетчик беззнаковых целых чисел, увеличиваемый при каждой транзакции. |
| 12 | isInit | Флаг «isInit». Равен 1, если файл shm инициализирован. |
| 13 | bigEndCksum | Равен true, если в файле WAL используются контрольные суммы с порядком байтов от старшего к младшему. Равен 0, если в WAL используются контрольные суммы с порядком байтов от младшего к старшему. |
| 14..15 | szPage | Размер страницы базы данных в байтах или 1, если размер страницы равен 65536. |
| 16..19 | mxFrame | Количество допустимых и зафиксированных кадров в файле WAL. |
| 20..23 | nPage | Размер файла базы данных в страницах. |
| 24..31 | aFrameCksum | Контрольная сумма последнего кадра в файле WAL. |
| 32..39 | aSalt | Два значения соли, скопированные из заголовка файла WAL. Эти значения имеют порядок байтов файла WAL, который может отличаться от естественного порядка байтов компьютера. |
| 40..47 | aCksum | Контрольная сумма байтов с 0 по 39 этого заголовка. |
| 48..95 | Копия байтов с 0 по 47 этого заголовка. | |
| 96..99 | nBackfill | Количество кадров WAL, ранее записанных в базу данных в ходе предыдущих контрольных точек. |
| 100..119 | read-mark[0..4] | Пять «меток чтения». Каждая метка чтения — это 32-разрядное беззнаковое целое число (4 байта). |
| 120..127 | Неиспользуемое пространство, выделенное для 8 файловых блокировок. | |
| 128..132 | nBackfillAttempted | Количество кадров WAL, для которых была предпринята попытка обратной записи, но которые могли быть записаны неуспешно. |
| 132..136 | Неиспользуемое пространство, зарезервированное для дальнейшего расширения. |
2.1.1. Поле mxFrame
32-разрядное беззнаковое целое число по смещению 16 (и повторно по смещению 64) — это количество допустимых кадров в WAL. Поскольку нумерация кадров WAL начинается с 1, mxFrame также является индексом последнего допустимого кадра фиксации в WAL. Кадр фиксации — это кадр, в котором в байтах с 4 по 7 заголовка кадра указано ненулевое значение «размера базы данных»; такой кадр обозначает конец транзакции.
Когда значение поля mxFrame равно нулю, это означает, что WAL пуст и все содержимое следует получать непосредственно из файла базы данных.
Когда mxFrame равно nBackfill, это означает, что все содержимое WAL уже записано обратно в базу данных. В этом случае все содержимое можно читать непосредственно из базы данных. Кроме того, следующий процесс записи может сбросить WAL, если никакие другие соединения не удерживают блокировки WAL_READ_LOCK(N) для N>0.
Значение mxFrame всегда больше или равно как nBackfill, так и nBackfillAttempted.
2.1.2. Поле nBackfill
32-разрядное беззнаковое целое число по смещению 128 в заголовке индекса WAL называется «nBackfill». Это поле содержит количество кадров в файле WAL, скопированных обратно в основную базу данных.
Значение nBackfill никогда не превышает mxFrame. Когда nBackfill равно mxFrame, это означает, что содержимое WAL полностью записано обратно в базу данных и WAL можно сбросить, если ни одна из блокировок WAL_READ_LOCK(N) для N>0 не удерживается.
Увеличивать nBackfill можно только при удержании WAL_CKPT_LOCK. Однако во время сброса WAL значение nBackfill изменяется на ноль; это происходит при удержании WAL_WRITE_LOCK.
2.1.3. Блокировки WAL
В заголовке выделено восемь байт для поддержки блокировки файлов с помощью метода xShmLock() объекта sqlite3_io_methods. SQLite никогда не читает и не записывает эти восемь байт, поскольку некоторые VFS (например, Windows) могут реализовывать блокировки с помощью обязательных файловых блокировок.
Поддерживаются следующие восемь блокировок:
Блокировки индекса WAL, управляемые с помощью xShmLock()| Имя | Смещение | |
|---|---|---|
| xShmLock | Файл | |
| WAL_WRITE_LOCK | 0 | 120 |
| WAL_CKPT_LOCK | 1 | 121 |
| WAL_RECOVER_LOCK | 2 | 122 |
| WAL_READ_LOCK(0) | 3 | 123 |
| WAL_READ_LOCK(1) | 4 | 124 |
| WAL_READ_LOCK(2) | 5 | 125 |
| WAL_READ_LOCK(3) | 6 | 126 |
| WAL_READ_LOCK(4) | 7 | 127 |
Дополнительно: сведения о заголовке
2.2. Хеш-таблицы индекса WAL
Хеш-таблицы в файле shm предназначены для быстрого ответа на следующий вопрос:
FindFrame(P,M): для заданного номера страницы P и максимального индекса кадра WAL M вернуть наибольший индекс кадра WAL для страницы P, не превышающий M, либо вернуть NULL, если кадров для страницы P с индексом не выше M нет.
Пусть типы данных «u8», «u16» и «u32» означают беззнаковые целые числа длиной соответственно 8, 16 и 32 бита. Тогда первый блок файла shm размером 32768 байт организован следующим образом:
u8 aWalIndexHeader[136]; u32 aPgno[4062]; u16 aHash[8192];
Второй и все последующие блоки файла shm размером 32768 байт устроены следующим образом:
u32 aPgno[4096]; u16 aHash[8192];
В совокупности записи aPgno содержат номера страниц базы данных, сохраненных во всех кадрах файла WAL. Запись aPgno[0] в первой хеш-таблице содержит номер страницы базы данных, сохраненной в самом первом кадре файла WAL. Запись aPgno[i] в первой хеш-таблице содержит номер страницы базы данных для i-го кадра файла WAL. Запись aPgno[k] во второй хеш-таблице содержит номер страницы базы данных для (k+4062)-го кадра файла WAL. Запись aPgno[k] в n-й хеш-таблице размером 32768 байт файла shm (для n>1) содержит номер страницы базы данных, сохраненной в (k+4062+4096*(n-2))-м кадре файла WAL.
Значения aPgno можно описать и немного иначе: если считать все значения aPgno непрерывным массивом, то номер страницы базы данных, сохраненный в i-м кадре файла WAL, хранится в aPgno[i]. Разумеется, aPgno не является непрерывным массивом. Первые 4062 записи находятся в первом блоке файла shm размером 32768 байт, а последующие значения размещаются блоками по 4096 записей в последующих блоках файла shm.
Один из способов вычислить FindFrame(P,M) — просканировать массив aPgno, начиная с M-й записи и двигаясь назад к началу, и вернуть J, для которого aPgno[J]==P. Такой алгоритм будет работать и окажется быстрее поиска последнего кадра с номером страницы P во всем файле WAL. Однако поиск можно значительно ускорить с помощью структуры aHash.
Номер страницы базы данных P преобразуется в хеш-значение с помощью следующей хеш-функции:
h = (P * 383)%8192
Эта функция отображает каждый номер страницы в целое число от 0 до 8191 включительно. Поле aHash каждого блока файла shm размером 32768 байт отображает значения P в индексы поля aPgno того же блока следующим образом:
- Вычислить хеш-значение: h = P * 383
- Пусть X — наибольшее множество последовательных целых чисел {h, h+1, h+2, ..., h+N}, для которого для каждого j из X выполняется aPgno[j%8192]!=0. Если aPgno[h%8192]==0, множество X будет пустым. Множество X легко вычислить, начав со значения h%8192, добавляя h%8192 в X и увеличивая h до тех пор, пока не встретится первая запись aPgno[h%8192], равная нулю.
- Множество X содержит индексы всех записей aPgno в текущем блоке файла shm размером 32768 байт, которые могут быть решением функции FindFrame(P,M). Каждую из этих записей нужно проверить отдельно, чтобы убедиться, что значение aPgno равно P, а номер кадра не превышает M. Ответом будет наибольший номер кадра, удовлетворяющий обоим условиям.
Каждой записи массива aPgno соответствует одна запись массива aHash. В aHash доступно больше позиций, чем в aPgno. Неиспользуемые позиции в aHash заполняются нулями. Поскольку в aHash гарантированно есть неиспользуемые позиции, цикл вычисления X гарантированно завершится. Ожидаемый размер X меньше 2. В худшем случае X будет иметь столько же элементов, сколько записей в aPgno, и тогда алгоритм будет работать примерно с той же скоростью, что и линейное сканирование aPgno. Однако такой худший случай встречается крайне редко. Обычно множество X невелико, а использование массива aHash позволяет вычислять FindFrame(P,M) гораздо быстрее.
Алгоритм хеш-поиска можно описать и альтернативным способом: начать с h = (P * 383)%8192 и просматривать aHash[h] и последующие записи, переходя к нулю при достижении h значения 8192, пока не будет найдена запись aHash[h]==0. Все записи aPgno с номером страницы P будут иметь индекс, совпадающий с одним из вычисленных таким образом значений aHash[h]. Однако не все вычисленные значения aHash[h] будут удовлетворять условиям совпадения, поэтому их нужно проверять по отдельности. Преимущество в скорости достигается за счет того, что обычно множество таких значений h очень мало.
Обратите внимание, что каждый блок файла shm размером 32768 байт содержит собственные массивы aHash и aPgno. Массив aHash отдельного блока помогает находить записи aPgno только в том же блоке. Для вычисления общей функции FindFrame(P,M) нужно начинать хеш-поиск с самого позднего блока и двигаться назад, к более ранним блокам, пока не будет найден ответ.
2.3. Матрица блокировок
Доступ в режиме WAL координируется с помощью как блокировок устаревшего режима DELETE, управляемых методами xLock и xUnlock объекта sqlite3_io_methods, так и блокировок WAL, управляемых методом xShmLock объекта sqlite3_io_methods.
Концептуально существует только одна блокировка режима DELETE. Блокировка режима DELETE для отдельного подключения к базе данных может находиться ровно в одном из следующих состояний:
- SQLITE_LOCK_NONE (не заблокировано)
- SQLITE_LOCK_SHARED (чтение)
- SQLITE_LOCK_RESERVED (чтение, ожидание записи)
- SQLITE_LOCK_PENDING (новые читатели заблокированы, ожидание записи)
- SQLITE_LOCK_EXCLUSIVE (запись)
Блокировки режима DELETE хранятся на странице байта блокировки основного файла базы данных. Для баз данных в режиме WAL имеют значение только SQLITE_LOCK_SHARED и SQLITE_LOCK_EXCLUSIVE. Другие состояния блокировки используются в режиме отката, но не в режиме WAL.
Блокировки режима WAL описаны выше.
2.3.1. Использование различных блокировок
Ниже приведены правила использования каждой блокировки.
-
SQLITE_LOCK_SHARED
Все подключения непрерывно удерживают SQLITE_LOCK_SHARED, пока подключены к базе данных в режиме WAL. Это относится как к подключениям с доступом для чтения и записи, так и к подключениям только для чтения. Блокировка SQLITE_LOCK_SHARED удерживается даже подключениями, не находящимися внутри транзакции. Это отличается от режима отката, в котором SQLITE_LOCK_SHARED снимается в конце каждой транзакции.
-
SQLITE_LOCK_EXCLUSIVE
Подключения удерживают эксклюзивную блокировку при переходе из режима WAL в любой из режимов отката или обратно. Подключения также могут пытаться получить эксклюзивную блокировку при отключении от режима WAL. Если подключению удается получить эксклюзивную блокировку, это означает, что оно является единственным подключением к базе данных и поэтому может попытаться выполнить контрольную точку, а затем удалить файлы индекса WAL и WAL.
Когда подключение удерживает общую блокировку основной базы данных, это не позволяет другим подключениям получить эксклюзивную блокировку. В свою очередь, это предотвращает удаление файлов индекса WAL и WAL, пока ими пользуются другие клиенты, и не позволяет перейти из режима WAL, пока другие клиенты обращаются к базе данных в этом режиме.
-
WAL_WRITE_LOCK
WAL_WRITE_LOCK блокируется только эксклюзивно. Общая блокировка WAL_WRITE_LOCK никогда не устанавливается.
Эксклюзивную WAL_WRITE_LOCK удерживает любое подключение, добавляющее содержимое в конец WAL. Следовательно, добавлять содержимое в WAL одновременно может только один процесс. Если в результате записи выполняется сброс WAL, поле nBackfill заголовка индекса WAL сбрасывается на ноль при удержании этой блокировки.
При выполнении восстановления общего индекса WAL подключение также удерживает эксклюзивную WAL_WRITE_LOCK и блокировки нескольких других байтов блокировки.
-
WAL_CKPT_LOCK
WAL_CKPT_LOCK блокируется только эксклюзивно. Общая блокировка WAL_CKPT_LOCK никогда не устанавливается.
Эксклюзивную WAL_CKPT_LOCK удерживает любое подключение, выполняющее контрольную точку. Поле nBackfill заголовка индекса WAL может увеличиваться при удержании этой эксклюзивной блокировки, но не может уменьшаться.
При выполнении восстановления общего индекса WAL подключение также удерживает эксклюзивную WAL_CKPT_LOCK и блокировки нескольких других байтов блокировки.
-
WAL_RECOVER_LOCK
WAL_RECOVER_LOCK блокируется только эксклюзивно. Общая блокировка WAL_RECOVER_LOCK никогда не устанавливается.
Эксклюзивную WAL_RECOVER_LOCK удерживает любое подключение, выполняющее восстановление для реконструкции общего индекса WAL.
Подключение только для чтения, перестраивающее собственный индекс WAL в памяти кучи, не удерживает эту блокировку. (Оно не может этого делать, поскольку подключениям только для чтения запрещено удерживать эксклюзивные блокировки.) Эта блокировка удерживается только при перестроении глобального общего индекса WAL, содержащегося в файле SHM, отображенном в память.
Помимо блокировки этого байта, подключение, выполняющее восстановление, также получает эксклюзивную блокировку всех остальных блокировок WAL, кроме WAL_READ_LOCK(0).
-
WAL_READ_LOCK(N)
Существует пять отдельных блокировок чтения с номерами от 0 до 4. Блокировки чтения могут быть как общими, так и эксклюзивными. Во время транзакции подключения получают общую блокировку одного из байтов блокировок чтения. Кроме того, подключения по очереди получают эксклюзивную блокировку чтения на короткий промежуток времени, пока обновляют соответствующие метки чтения. При выполнении восстановления блокировки чтения с 1 по 4 удерживаются эксклюзивно.
Каждый байт блокировки чтения соответствует одному из пяти 32-разрядных целых чисел меток чтения, расположенных в байтах с 100 по 119 заголовка индекса WAL, следующим образом:
Имя блокировки Смещение блокировки Имя метки чтения Смещение метки чтения WAL_READ_LOCK(0) 123 read-mark[0] 100..103 WAL_READ_LOCK(1) 124 read-mark[1] 104..107 WAL_READ_LOCK(2) 125 read-mark[2] 108..111 WAL_READ_LOCK(3) 126 read-mark[3] 112..115 WAL_READ_LOCK(4) 127 read-mark[4] 116..119 Когда подключение удерживает общую блокировку WAL_READ_LOCK(N), оно гарантирует, что для любых страниц базы данных, измененных первыми read-mark[N] записями в WAL, будет использоваться WAL, а не файл базы данных. Значение read-mark[0] всегда равно нулю. Если подключение удерживает общую блокировку WAL_READ_LOCK(0), это означает, что оно рассчитывает игнорировать WAL и читать любое нужное ему содержимое из основной базы данных. Если N>0, подключение может при необходимости использовать и другие данные файла WAL за пределами read-mark[N], вплоть до первых mxFrame кадров. Однако, удерживая общую блокировку WAL_READ_LOCK(0), подключение гарантирует, что никогда не будет читать содержимое из WAL и будет получать все данные непосредственно из основной базы данных.
Если при выполнении контрольной точки обнаруживается блокировка WAL_READ_LOCK(N), нельзя переносить содержимое WAL в основную базу данных дальше первых read-mark[N] кадров. В противном случае будут перезаписаны данные, которые процесс, удерживающий блокировку, рассчитывал прочитать из основного файла базы данных. Следовательно, если файл WAL содержит больше кадров, чем read-mark[N] (если mxFrame>read-mark[N] для любой метки чтения, блокировка WAL_READ_LOCK(N) которой удерживается другим процессом), контрольную точку нельзя завершить.
Если процессу записи требуется сбросить WAL, он должен убедиться, что нет блокировок WAL_READ_LOCK(N) для N>0, поскольку такие блокировки означают, что другое подключение еще использует текущий файл WAL, а сброс WAL удалил бы данные, необходимые этим подключениям. Сброс WAL допустим, если другие подключения удерживают WAL_READ_LOCK(0), поскольку, удерживая WAL_READ_LOCK(0), они гарантируют, что не будут использовать никакие данные из WAL.
2.3.2. Операции, требующие блокировок, и используемые ими блокировки
-
Переход в режим WAL и выход из него
Подключение, которому требуется перейти в режим WAL или выйти из него, должно удерживать блокировку SQLITE_LOCK_EXCLUSIVE. Поэтому переход в режим WAL аналогичен любой другой транзакции записи, поскольку для каждой транзакции записи в режиме отката требуется блокировка SQLITE_LOCK_EXCLUSIVE. Если файл базы данных уже работает в режиме WAL (то есть требуется вернуть его в режим отката) и к базе данных подключены два или более клиентов, каждый из них будет удерживать блокировку SQLITE_LOCK_SHARED. Это означает, что получить SQLITE_LOCK_EXCLUSIVE нельзя и выход из режима WAL будет запрещен. Это не позволяет одному подключению удалить режим WAL, пока им пользуется другое. Следовательно, перевести базу данных из режима WAL в режим отката можно только после закрытия всех подключений к базе данных, кроме одного.
-
Закрытие подключения к базе данных в режиме WAL
При закрытии подключения к базе данных (с помощью sqlite3_close() или sqlite3_close_v2()) предпринимается попытка получить SQLITE_LOCK_EXCLUSIVE. Если эта попытка успешна, значит, закрывающееся подключение является последним подключением к базе данных. В этом случае желательно очистить файлы WAL и индекса WAL, поэтому закрывающееся подключение выполняет контрольную точку (удерживая SQLITE_LOCK_EXCLUSIVE), после чего удаляет оба файла — WAL и индекса WAL. SQLITE_LOCK_EXCLUSIVE не снимается до удаления обоих файлов.
Если приложение вызывает sqlite3_file_control(SQLITE_FCNTL_PERSIST_WAL) для подключения к базе данных перед его закрытием, последняя контрольная точка все равно выполняется, но файлы WAL и индекса WAL не удаляются, как это обычно происходит. В результате база данных остается в состоянии, позволяющем другим процессам без разрешения на запись в файлы базы данных, WAL или индекса WAL открыть базу данных только для чтения. Если файлы WAL и индекса WAL отсутствуют, процесс, у которого нет разрешения на создание и инициализацию этих файлов, не сможет открыть базу данных, если только она не объявлена неизменяемой с помощью параметра запроса immutable.
-
Реконструкция глобального общего индекса WAL при восстановлении
При реконструкции глобального общего индекса WAL в ходе восстановления все блокировки индекса WAL, кроме WAL_READ_LOCK(0), удерживаются эксклюзивно.
-
Добавление новой транзакции в конец WAL
При добавлении новых кадров в конец файла WAL на WAL_WRITE_LOCK устанавливается эксклюзивная блокировка.
Чтение данных из базы данных и WAL в рамках транзакции
Выполнение контрольной точки
-
Сброс файла WAL
Сброс WAL означает перемотку WAL к началу и начало добавления новых кадров с самого начала. Это происходит при добавлении новых кадров в WAL, у которого mxFrame равен nBackfill и на который не установлены блокировки от WAL_READ_LOCK(1) до WAL_READ_LOCK(4). При этом удерживается WAL_WRITE_LOCK.
3. Восстановление
Восстановление — это процесс перестроения индекса WAL для его синхронизации с WAL.
Восстановление выполняется первым потоком, подключившимся к базе данных в режиме WAL. Оно восстанавливает WAL-индекс, чтобы тот точно описывал файл WAL. Если в момент подключения первого потока к базе данных файл WAL отсутствует, восстанавливать нечего, однако процесс восстановления всё равно запускается для инициализации WAL-индекса.
Если WAL-индекс реализован как файл с отображением в память и этот файл доступен первому подключившемуся потоку только для чтения, поток создаёт частный WAL-индекс в динамической памяти в качестве замены и запускает процедуру восстановления для его заполнения. В результате получаются те же данные, но они хранятся в частном пространстве, а не записываются в общую область памяти.
Восстановление выполняется за один проход по WAL от начала до конца. При чтении каждого кадра WAL проверяются контрольные суммы. Сканирование прекращается в конце файла или при обнаружении первой недействительной контрольной суммы. Поле mxFrame устанавливается в индекс последнего действительного кадра фиксации в WAL. Поскольку нумерация кадров WAL начинается с 1, mxFrame также равен количеству действительных кадров в WAL. «Кадр фиксации» — это кадр, в байтах с 4-го по 7-й заголовка которого хранится ненулевое значение. Поскольку процедура восстановления не может определить, сколько кадров WAL могло быть ранее скопировано обратно в базу данных, она устанавливает значение nBackfill в ноль.
Во время восстановления глобального WAL-индекса в общей памяти удерживаются эксклюзивные блокировки WAL_WRITE_LOCK, WAL_CKPT_LOCK, WAL_RECOVER_LOCK и WAL_READ_LOCK(1)–WAL_READ_LOCK(4). Иными словами, все связанные с WAL-индексом блокировки, кроме WAL_READ_LOCK(0), удерживаются в эксклюзивном режиме. Это не позволяет другим потокам записывать данные в базу данных и читать транзакции, хранящиеся в WAL, до завершения восстановления.
Эта страница в последний раз обновлялась 2025-05-10 11:18:57Z
SQLite is in the Public Domain.
https://sqlite.org/walformat.html