![]() |
|
|||
WebMoney: WMZ Z294115950220 WMR R409981405661 WME E134003968233 |
Visa 4274 3200 2453 6495 |
Этот документ был первоначально создан в начале 2004 года, когда SQLite
version 2 была все еще в широком использовании и был
написан, чтобы ввести новое понятие SQLite version 3
читателям, которые были уже знакомы с версией 2 SQLite. Но в эти дни
большинство читателей этого документа, вероятно, никогда не видело версию 2
SQLite и знакомы только с версией 3 SQLite. Тем не менее, этот документ
продолжает служить авторитетным источником о том, как захват файла базы
данных работает в версии 3 SQLite. Документ только описывает захват для более старого механизма транзакций
режима обратной перемотки. Захват для более нового
журнала с упреждающей записью или режима WAL
описан отдельно. SQLite Version 3.0.0
ввел новый механизм захвата и журналирования, разработанный, чтобы улучшить
параллелизм по версии 2 SQLite и уменьшить проблему простоя записи.
Новый механизм также позволяет атомную передачу транзакций, включающих
многократные файлы базы данных. Этот документ описывает новый механизм
захвата. Целевая аудитория: программисты, которые хотят понять и/или изменить
код, и рецензенты, работающие, чтобы проверить дизайн версии 3 SQLite. Захват и управление совместным выполнением обработан
страничным
модулем. Он ответственен за то, чтобы сделалть "ACID" SQLite
(Atomic, Consistent, Isolated and Durable).
Модуль удостоверяется, что если изменения происходят, то все изменения
происходят, или ни одно из них не происходит, два или больше процесса не
пытаются получить доступ к базе данных несовместимыми способами в то же
время, и что как только изменения были написаны, они постоянны
пока явно не удалены. Модуль также обеспечивает кэш памяти для части
содержания дискового файла. Модуль равнодушен к деталям B-деревьев, текстового кодирования, индексов
и т. д. С его точки зрения
база данных состоит из единственного файла блоков однородного размера.
Каждый блок называют "страницей" и обычно по 1024 байта в размере.
Страницы пронумерованы с 1. Таким образом, первые 1024 байта базы данных
называют "страницей 1", вторые 1024 байта "страницей 2"
и т. д. Все другие детали кодирования обработаны более высокими слоями
библиотеки. Модуль общается с операционной системой, используя один из
нескольких модулей (примеры:
os_unix.c,
os_win.c), который обеспечивает однородную абстракцию для
служб операционной системы. Модуль страниц эффективно управляет доступом для отдельных потоков
или процессов. Всюду по этому документу каждый раз, когда слово
"процесс" написано, его можно заменить словом "поток",
не изменяя истинность заявления. С точки зрения единственного процесса файл базы данных может быть в одном
из пяти состояний захвата: Слой интерфейса операционной системы понимает и отслеживает все пять
состояний захвата, описанных выше. Модуль страниц отслеживает четыре из пяти
состояний захвата. PENDING всегда просто временная стартовая площадка на пути
к EXCLUSIVE и таким образом, модуль не отслеживает PENDING. Когда процесс хочет изменить файл базы данных (и это не находится в
режиме WAL), это сначала делает запись оригинального
неизменного содержания базы данных в журнале обратной перемотки.
Журнал обратной перемотки это обычный дисковый файл, который всегда
располагается в том же самом каталоге как файл базы данных и имеет то же
самое имя как файл базы данных с добавлением суффикса -journal.
Журнал обратной перемотки также делает запись начального размера базы данных
так, чтобы, если файл базы данных растет, это могло быть усеченно
к своему первоначальному размеру на обратной перемотке. Если SQLite работает с многократными базами данных в то же время
(используя команду ATTACH),
у каждой базы данных есть свой собственный журнал обратной перемотки.
Но есть также отдельный совокупный журнал, названный супержурналом.
Супержурнал не содержит данные о странице, используемые для того, чтобы
отменить изменения . Вместо этого супержурнал содержит названия отдельных
журналов обратной перемотки базы данных для каждой из ATTACH-баз данных.
Каждый из отдельных журналов обратной перемотки базы данных также содержит
название супержурнала. Если нет никаких баз данных ATTACH
(или если ни одна из базы данных ATTACH не участвует в текущей
транзакции), никакой супержурнал не создается, и нормальный журнал обратной
перемотки содержит пустую строку в месте, обычно зарезервированном для
записи названия супержурнала. Журнал обратной перемотки, как говорят,
горячий, если он должен быть
отменен, чтобы восстановить целостность базы данных.
Горячий журнал создается, когда процесс отвалился посреди обновления базы
данных. Горячие журналы это условие исключений.
Горячие журналы существуют, чтобы прийти в себя после катастроф и перебоев в
питании. Если все будет работать правильно (то есть, если не будет никаких
катастроф или перебоев в питании), то вы никогда не будете
получать горячий журнал. Если никакой супержурнал не включается, то журнал горячий, если он
существует и имеет заголовок отличный от нуля, и у его соответствующего файла
базы данных нет блокировки RESERVED.
Если супержурнал называют в журнале файла, то журнал файла горячий, если его
супержурнал существует и нет никакой блокировки RESERVED на соответствующем
файле базы данных. Важно понять, когда журнал горячий, таким образом,
предыдущие правила будут повторены: Прежде, чем читать от файла базы данных, SQLite всегда проверяет, чтобы
видеть, есть ли у того файла базы данных горячий журнал. Если у файла
действительно есть горячий журнал, то журнал отменен
прежде чем файл прочитан. Таким образом мы гарантируем, что файл базы
данных находится в последовательном состоянии, прежде чем
он будет прочитан. Когда процесс хочет читать файла базы данных, он делает так: После того, как алгоритм выше заканчивает успешно, безопасно читать из
файла базы данных. Как только все чтение закончилось,
снимается блокировка SHARED. Несвежий супержурнал это супержурнал, который больше не используется ни
для чего. Нет никакого требования, что несвежие супержурналы должны быть
удалены. Единственная причина того, чтобы сделать так состоит в том, чтобы
освободить дисковое пространство. Супержурнал несвежий, если никакие отдельные журналы файла не указывают
на него. Чтобы выяснить, что супержурнал несвежий, мы сначала читаем
супержурнал, чтобы получить названия всех его журналов файла.
Тогда мы проверяем каждый из тех журналов файла. Если какой-либо из журналов
файла, названных в супержурнале, существует и указывает назад на супержурнал,
то супержурнал не несвежий. Если все журналы файла отсутствуют или обращаются
к другим супержурналам или никакому супержурналу вообще, то супержурнал,
который мы проверяем, несвежий и может быть безопасно удален. Чтобы написать базу данных, процесс должен сначала приобрести блокировку
SHARED, как описано выше (возможна отмена
неполных изменений, если есть горячий журнал). После того, как блокировка
SHARED получена, блокировка RESERVED должна быть приобретена.
RESERVED сигнализирует, что процесс намеревается писать базу данных в
какой-то момент в будущем. Только один процесс за один раз может держать
блокировку RESERVED. Но другие процессы могут продолжить читать базу данных,
в то время как есть блокировка RESERVED. Если процесс, который хочет написать, неспособен получить блокировку
RESERVED, это должно означать, что у другого процесса уже есть блокировка
RESERVED. В этом случае написать попытка возвращает SQLITE_BUSY. После получения RESERVED процесс, который хочет написать, создает журнал
обратной перемотки. Заголовок журнала инициализируется с первоначальным
размером файла базы данных. Пространство в заголовке журнала также
резервируется для названия супержурнала, хотя название
супержурнала первоначально пусто. Прежде, чем внести изменения в любую страницу базы данных, процесс пишет
оригинальное содержание той страницы в журнал обратной перемотки.
Изменения страниц проводятся в памяти сначала и не написаны на диск.
Оригинальный файл базы данных остается неизменным, что означает, что другие
процессы могут продолжить читать базу данных. В конечном счете запись захочет обновить файл базы данных, потому что его
кэш памяти заполнился или потому что это готово передать свои изменения.
Прежде чем это происходит, писатель должен удостовериться, что никакой другой
процесс не читает базу данных и что данные о журнале обратной перемотки
находятся безопасно на диске так, чтобы это могло
отменить неполные изменения в случае перебоя в питании. Шаги следующие: Если причина записи файла базы данных будет состоять в том,
что кэш памяти был полон, то писатель не передаст транзакцию сразу же.
Вместо этого писатель мог бы продолжить вносить изменения в другие страницы.
Прежде чем последующие изменения написаны файлу базы данных, журнал обратной
перемотки должен быть сброшен на диск. Отметьте также, что блокировка
EXCLUSIVE, которую получил писатель, чтобы написать базу данных
первоначально, должна быть проведена, пока все изменения не передаются.
Это означает, что никакие другие процессы не в состоянии получить доступ к
базе данных с того времени, когда кэш памяти сначала пишется на диск и пока
транзакция не передается. Когда писатель готов передать его изменения, это
выполняет следующие шаги: Как только блокировка PENDING снята с файла базы данных, другие процессы
могут начать читать базу данных снова. В текущем внедрении также снимается
RESERVED, но это неважно для правильной операции. Если транзакция включает многократные базы данных, то более сложная
последовательность передачи используется следующим образом: В SQLite version 2 если много процессов читают от базы данных, могло бы
иметь место, что никогда нет времени, когда нет никаких активных читателей.
И если всегда есть по крайней мере одно соединение, которое читает базу
данных, никакой процесс никогда не был бы в состоянии внести изменения в базу
данных, потому что будет невозможно приобрести блокировку записи. SQLite version 3 стремится избежать этого с помощью блокировки PENDING.
Она позволяет существующим читателям продолжать, но препятствует тому, чтобы
новые читатели соединились с базой данных. Таким образом, когда процесс хочет
написать занятую базу данных, он может установить PENDING, которая будет
препятствовать тому, чтобы появились новые читатели. Существующие читатели
действительно в конечном счете заканчивают, все блокировки SHARED
в конечном счете очистятся, и писателю дадут шанс внести его изменения. Страничный модуль очень прочен, но он может быть поврежден.
Эта секция пытается определить и объяснить риски. См. также
здесь. Очевидно, ошибка аппаратной или операционной системы, которая вводит
неправильные данные в середину файла базы данных или журнала, вызовет
проблемы. Аналогично, если процесс откроет файл базы данных или журнал и
напишет некорректные данные в середину его, то база
данных станет поврежденной. SQLite применяет консультативные блокировки POSIX, чтобы осуществить
блокировку в Unix. В Windows применяется LockFile(), LockFileEx() и
UnlockFile(). SQLite предполагает что эти системные вызовы работают, как
рекламируется. Если это не так, тогда возможно повреждение базы данных.
Нужно отметить, что консультативная блокировка POSIX, как известно, является
ненадежной или даже не осуществляется на многих внедрениях NFS (включая
последние версии Mac OS X) и что есть сообщения о проблемах для сетевых
файловых систем под Windows. Ваша лучшая защита это не использовать SQLite
для файлов в сетевой файловой системе. SQLite применяет fsync() для сброса данных на диск в Unix и
FlushFileBuffers() в Windows. SQLite предполагает, что эти службы
операционной системы функционируют, как рекламируется. Но было сообщено, что
fsync() и FlushFileBuffers() не всегда работают правильно, особенно с
некоторыми сетевыми файловыми системами или недорогими дисками IDE.
По-видимому, у некоторых изготовителей дисков IDE есть чипы контроллера,
которые сообщают, что данные достигли поверхности диска, когда на самом деле
данные находятся все еще в изменчивой кэш-памяти в электронике дисковода.
Есть также отчеты, что Windows иногда принимает решение проигнорировать
FlushFileBuffers() по неуказанным причинам. Автор не может проверить ни один
из этих отчетов. Но если они верны, это означает, что повреждение
базы данных возможно после неожиданных потерь питания.
Это ошибки аппаратной и/или операционной системы, от которых
SQLite неспособен защитить. Если Linux ext3
смонтирована без опции "barrier=1" в
/etc/fstab
и кэш записи дисковода позволен, тогда повреждение
файловой системы может произойти после катастрофы OS или потерь питания.
Может ли повреждение произойти, зависит от деталей дисковых аппаратных
средств контроля, это более вероятно с недорогими дисками потребительского
сорта и меньше количество проблем для устройств хранения данных промышленного
класса с расширенными функциями, такими как энергонезависимые кэши записи.
Различные эксперты по ext3
подтверждают это поведение.
Нам говорят, что большинство дистрибутивов Linux не использует barrier=1 и не
отключает кэш записи, таким образом, большинство дистрибутивов Linux уязвимо
для этой проблемы. Обратите внимание на то, что это аппаратная проблема
и что нет ничего, что SQLite может сделать, чтобы работать вокруг этого.
Другие СУБД также столкнулись с этой проблемой. Если сбой произойдет и приведет к горячему журналу, но тот журнал удален,
следующим процесс, который откроет базу данных, но не будет знать, что это
содержит изменения, которые должны быть отменены. Обратная перемотка не
произойдет, и базу данных оставят в непоследовательном состоянии.
Журналы обратной перемотки могли бы быть удалены по разным причинам: Последняя (четвертая) ситуация выше заслуживает дополнительный
комментарий. Когда SQLite создает файл журнала в Unix, он открывает каталог,
который содержит тот файл и вызывает fsync()
на каталоге, чтобы выдвинуть информацию о нем на диск.
Но предположите, что некоторый другой процесс добавляет или удаляет
несвязанные файлы к каталогу, который содержит базу данных и журнал во время
перебоя в питании. Предположительно, несвязанные действия этого другого
процесса могли бы привести к файлу журнала, исключаемому из каталога
и перемещенному в "lost+found". Это маловероятный сценарий, но это могло
произойти. Лучшая обороноспособность должна использовать
журналируемую файловую систему. Для коммита, затрагивающего много БД и супержурнал,
если различные базы данных были на различных дисковых томах, и сбой питания
происходит, диски могли бы быть повторно установлены с различными именами.
Или некоторые диски не могли бы быть установлены вообще.
Когда это происходит, отдельные журналы файла и супержурнал не могли бы быть
в состоянии найти друг друга. Худший результат из этого сценария:
передача прекращает быть атомной. Некоторые базы данных могли бы быть
отменены до прежнего уровня, и другие не могли бы. Все базы данных продолжат
быть последовательными. Чтобы защититься от этой проблемы, держите все базы
данных на том же самом дисковом томе и/или перемонтируйте диски, используя
точно те же самые имена после перебоя в питании. Изменения захвата и управления совместным выполнением в версии SQLite
version 3 также вводят некоторые тонкие изменения в способе, которым
транзакции работают на языковом уровне SQL. По умолчанию версия 3 SQLite
работает в режиме autocommit. В нем все изменения базы данных
передаются, как только все операции, связанные с текущим соединением с
базой данных, заканчиваются. SQL-команда "BEGIN TRANSACTION" (TRANSACTION опционально)
используется, чтобы вывести SQLite из режима autocommit.
Обратите внимание на то, что команда BEGIN не приобретает никакой
блокировки на БД. После команды BEGIN блокировка SHARED
будет приобретена, когда первый оператор SELECT будет выполнен.
Блокировка RESERVED будет приобретен, когда первый INSERT, UPDATE или
оператор удаления будут выполнены. Никакая блокировка EXCLUSIVE
не приобретена пока кэш памяти заполняется и должен быть переписан на диск
или пока транзакция не передается. SQL-команда "COMMIT" на самом деле не передает изменения на диск.
Это просто определяет autocommit = on.
Затем в конце команды логика autocommit вступает в действие
и вызывает фактическую передачу на диск. SQL-команда "ROLLBACK"
также работает, указывая autocommit = on,
но она также устанавливает флаг, который включает логику
обратной перемотки, а не передачи. Если SQL COMMIT ставит autocommit = on,
и логика пытается передать изменение, но терпит неудачу, потому что некоторый
другой процесс держит блокировку SHARED, autocommit автоматически ставится
назад в off. Это позволяет пользователю повторять COMMIT в более позднее
время после того, как снимется блокировка SHARED. Если многократные команды выполняются против того же самого соединения с
базой данных SQLite в то же время, автопередача отсрочена, пока самая
последняя команда не заканчивает. Например, если оператор SELECT будет
выполнен, выполнение команды сделает паузу, когда каждая строка
результата возвращен. Во время этой паузы другие команды INSERT, UPDATE или
DELETE могут быть выполнены для других таблиц
в базе данных. Но ни одно из этих изменений не передается,
пока оригинальный оператор SELECT не заканчивается.
Choose any three.
1.0. Захват файла и параллелизм в версии 3 SQLite
2.0. Обзор
3.0. Блокировка
4.0. Журнал обратной перемотки
4.1. Контакт с горячими журналами
4.2. Удаление несвежих супержурналов
5.0. Запись файла базы данных
5.1. Проблемы записи
6.0. Как угрохать файлы базы данных
7.0. Управление транзакциями на уровне SQL