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

Small. Fast. Reliable.
Choose any three.

Этот документ был первоначально создан в начале 2004 года, когда SQLite version 2 была все еще в широком использовании и был написан, чтобы ввести новое понятие SQLite version 3 читателям, которые были уже знакомы с версией 2 SQLite. Но в эти дни большинство читателей этого документа, вероятно, никогда не видело версию 2 SQLite и знакомы только с версией 3 SQLite. Тем не менее, этот документ продолжает служить авторитетным источником о том, как захват файла базы данных работает в версии 3 SQLite.

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

1.0. Захват файла и параллелизм в версии 3 SQLite

SQLite Version 3.0.0 ввел новый механизм захвата и журналирования, разработанный, чтобы улучшить параллелизм по версии 2 SQLite и уменьшить проблему простоя записи. Новый механизм также позволяет атомную передачу транзакций, включающих многократные файлы базы данных. Этот документ описывает новый механизм захвата. Целевая аудитория: программисты, которые хотят понять и/или изменить код, и рецензенты, работающие, чтобы проверить дизайн версии 3 SQLite.

2.0. Обзор

Захват и управление совместным выполнением обработан страничным модулем. Он ответственен за то, чтобы сделалть "ACID" SQLite (Atomic, Consistent, Isolated and Durable). Модуль удостоверяется, что если изменения происходят, то все изменения происходят, или ни одно из них не происходит, два или больше процесса не пытаются получить доступ к базе данных несовместимыми способами в то же время, и что как только изменения были написаны, они постоянны пока явно не удалены. Модуль также обеспечивает кэш памяти для части содержания дискового файла.

Модуль равнодушен к деталям B-деревьев, текстового кодирования, индексов и т. д. С его точки зрения база данных состоит из единственного файла блоков однородного размера. Каждый блок называют "страницей" и обычно по 1024 байта в размере. Страницы пронумерованы с 1. Таким образом, первые 1024 байта базы данных называют "страницей 1", вторые 1024 байта "страницей 2" и т. д. Все другие детали кодирования обработаны более высокими слоями библиотеки. Модуль общается с операционной системой, используя один из нескольких модулей (примеры: os_unix.c, os_win.c), который обеспечивает однородную абстракцию для служб операционной системы.

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

3.0. Блокировка

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

UNLOCKED База данных не может быть ни прочитана, ни написана. Любые внутренне кэшированные данные считают подлежащими проверке. Другие процессы могут прочитать или написать базу данных. Это состояние по умолчанию.
SHARED База данных может быть прочитана, но не написана. Любое количество процессов может держать блокировку SHARED в то же время, следовательно могут быть многие одновременные читатели. Но никакому другому процессу не позволяют написать файл базы данных в то время, как одна или более блокировок SHARED активна.
RESERVED Блокировка RESERVED означает, что процесс это планирование записи файла базы данных в какой-то момент в будущем, но что это в настоящее время просто читает файл. Только одна блокировка RESERVED может быть активна когда-либо, хотя многократные блокировки SHARED могут сосуществовать с единственной RESERVED. RESERVED отличается от PENDING в том, что новые блокировки SHARED могут быть приобретены, в то время как есть RESERVED.
PENDING PENDING означает, что процесс, держащий блокировку, хочет написать базу данных как можно скорее и просто ждет на всех текущих блокировках SHARED их снятия, чтобы это могло получить блокировку EXCLUSIVE. Никакие новые блокировки SHARED не разрешены для базы данных, если PENDING активна, хотя существующим блокировкам SHARED позволяют продолжиться.
EXCLUSIVE EXCLUSIVE необходима, чтобы написать файл базы данных. Только одна EXCLUSIVE позволена на файле, и никаким другим блокировкам любого вида не позволяют сосуществовать с EXCLUSIVE. Чтобы максимизировать параллелизм, SQLite работает, чтобы минимизировать количество времени, которое используют блокировки EXCLUSIVE.

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

4.0. Журнал обратной перемотки

Когда процесс хочет изменить файл базы данных (и это не находится в режиме WAL), это сначала делает запись оригинального неизменного содержания базы данных в журнале обратной перемотки. Журнал обратной перемотки это обычный дисковый файл, который всегда располагается в том же самом каталоге как файл базы данных и имеет то же самое имя как файл базы данных с добавлением суффикса -journal. Журнал обратной перемотки также делает запись начального размера базы данных так, чтобы, если файл базы данных растет, это могло быть усеченно к своему первоначальному размеру на обратной перемотке.

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

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

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

  • Журнал горячий если...
    • Это существует, и
    • Его размер больше, чем 512 байтов, и
    • Заголовок журнала отличный от нуля и правильно построенный, и
    • Его супержурнал существует или название супержурнала это пустая строка, и
    • Нет никакой блокировки RESERVED на соответствующем файле базы данных.

4.1. Контакт с горячими журналами

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

Когда процесс хочет читать файла базы данных, он делает так:

  1. Откройте файл базы данных и получите блокировку SHARED. Если она не может быть получена, надо немедленно потерпеть неудачу и возвратить SQLITE_BUSY.
  2. Проверьте, чтобы видеть, есть ли у файла базы данных горячий журнал. Если у файла нет горячего журнала, все в порядке. Возвратитесь немедленно. Если есть горячий журнал, тот журнал должен быть отменен последующими шагами этого алгоритма.
  3. Приобретите блокировку PENDING, затем EXCLUSIVE на файле базы данных. Отметьте: не приобретайте RESERVED потому что это заставило бы другие процессы думать, что журнал больше не был горячим. Если мы не приобретаем эти блокировки, это означает, что другой процесс уже пытается сделать обратную перемотку. В этом случае снимите все блокировки, закройте базу данных и возвратите SQLITE_BUSY.
  4. Прочитайте файл журнала и отмените изменения.
  5. Ждите записи на диск отмененных изменений. Это защищает целостность базы данных в случае, если другой перебой в питании или катастрофа происходят.
  6. Удалите файл журнала (или усеките журнал к нулю байт в длину, если PRAGMA journal_mode=TRUNCATE или обнулите заголовок журнала, если PRAGMA journal_mode=PERSIST).
  7. Удалите файл супержурнала, если безопасно сделать так. Этот шаг дополнительный. Это здесь только, чтобы препятствовать тому, чтобы несвежие супержурналы загромоздили диск. Посмотрите обсуждение ниже для деталей.
  8. Снимите EXCLUSIVE и PENDING, но оставьте блокировку SHARED.

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

4.2. Удаление несвежих супержурналов

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

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

5.0. Запись файла базы данных

Чтобы написать базу данных, процесс должен сначала приобрести блокировку SHARED, как описано выше (возможна отмена неполных изменений, если есть горячий журнал). После того, как блокировка SHARED получена, блокировка RESERVED должна быть приобретена. RESERVED сигнализирует, что процесс намеревается писать базу данных в какой-то момент в будущем. Только один процесс за один раз может держать блокировку RESERVED. Но другие процессы могут продолжить читать базу данных, в то время как есть блокировка RESERVED.

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

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

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

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

  1. Удостоверьтесь, что все данные о журнале обратной перемотки были на самом деле написаны на диск (а не в кэш).
  2. Получите блокировку PENDING, а затем EXCLUSIVE на файле базы данных. Если у других процессов все еще есть блокировки SHARED, писателю, возможно, придется ждать их снятия, прежде чем это будет в состоянии получить EXCLUSIVE.
  3. Напишите все модификации страницы, в настоящее время проводимые в памяти, в дисковый файл базы данных.

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

Когда писатель готов передать его изменения, это выполняет следующие шаги:

  1. Получите блокировку EXCLUSIVE на файле базы данных и удостоверьтесь, что все изменения памяти были написаны в файл базы данных, используя алгоритм шагов 1-3 выше.
  2. Сбросьте все изменения файла базы данных на диск.
  3. Удалите файл журнала. Или если PRAGMA journal_mode = TRUNCATE или PERSIST, усеките файл журнала или обнулите заголовок файла журнала, соответственно. Это момент, когда изменения передаются. До удаления файла журнала, если перебой в питании происходит, следующий процесс, который откроет базу данных, будет видеть, что это имеет горячий журнал и отменит изменения. После того, как журнал удален, больше не будет горячего журнала, и изменения сохранятся.
  4. Снимите блокировки EXCLUSIVE и PENDING с файла базы данных.

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

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

  1. Удостоверьтесь, что у всех отдельных файлов базы данных есть блокировка EXCLUSIVE и действительный журнал.
  2. Создайте супержурнал. Название супержурнала произвольно. Текущее внедрение прилагает случайные суффиксы к названию главного файла базы данных, пока это не находит имя, которое еще не существует. Заполните супержурнал названиями всех отдельных журналов и сбросьте его на диск.
  3. Напишите имя супержурнала во все отдельные журналы (в месте, выделенном с этой целью в заголовках отдельных журналов) и сбросьте содержание отдельных журналов на диск.
  4. Сбросьте все изменения файла базы данных на диск.
  5. Удалите файл супержурнала. Это момент, когда изменения передаются. До удаления файла супержурнала, если перебой в питании происходит, отдельные журналы файла будут считаться горячими, и их отменит следующий процесс, который попытается прочитать их. После того, как супержурнал был удален, журналы файла больше не будут считаться горячими, и изменения сохранятся.
  6. Удалите все отдельные файлы журнала.
  7. Снимите блокировки EXCLUSIVE и PENDING со всех файлов базы данных.

5.1. Проблемы записи

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

SQLite version 3 стремится избежать этого с помощью блокировки PENDING. Она позволяет существующим читателям продолжать, но препятствует тому, чтобы новые читатели соединились с базой данных. Таким образом, когда процесс хочет написать занятую базу данных, он может установить PENDING, которая будет препятствовать тому, чтобы появились новые читатели. Существующие читатели действительно в конечном счете заканчивают, все блокировки SHARED в конечном счете очистятся, и писателю дадут шанс внести его изменения.

6.0. Как угрохать файлы базы данных

Страничный модуль очень прочен, но он может быть поврежден. Эта секция пытается определить и объяснить риски. См. также здесь.

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

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.
  • Повреждение файловой системы после перебоя в питании могло бы заставить журнал быть переименованным или удаленным.

Последняя (четвертая) ситуация выше заслуживает дополнительный комментарий. Когда SQLite создает файл журнала в Unix, он открывает каталог, который содержит тот файл и вызывает fsync() на каталоге, чтобы выдвинуть информацию о нем на диск. Но предположите, что некоторый другой процесс добавляет или удаляет несвязанные файлы к каталогу, который содержит базу данных и журнал во время перебоя в питании. Предположительно, несвязанные действия этого другого процесса могли бы привести к файлу журнала, исключаемому из каталога и перемещенному в "lost+found". Это маловероятный сценарий, но это могло произойти. Лучшая обороноспособность должна использовать журналируемую файловую систему.

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

7.0. Управление транзакциями на уровне SQL

Изменения захвата и управления совместным выполнением в версии 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 не заканчивается.