Spec-Zone.ru › SQLite

Как испортить файл базы данных SQLite

Содержание
1. Перезапись файла вредоносным потоком или процессом
1.1. Продолжение использования дескриптора файла после его закрытия
1.2. Архивирование или восстановление во время активной транзакции
1.3. Удаление активного журнала
1.4. Несоответствие файлов базы данных и активных журналов
2. Проблемы с блокировкой файлов
2.1. Файловые системы с неисправными или отсутствующими реализациями блокировки
2.2. Отмена POSIX-консультативных блокировок отдельным потоком, выполняющим close()
2.2.1. Несколько копий SQLite, связанных с одним приложением
2.3. Два процесса, использующих разные протоколы блокировки
2.4. Отключение или переименование файла базы данных во время использования
2.5. Несколько ссылок на один и тот же файл
2.6. Перенос открытого подключения к базе данных через fork()
3. Отсутствие синхронизации
3.1. Жесткие диски, не выполняющие запросы синхронизации
3.2. Отключение синхронизации с помощью PRAGMAs
4. Неисправности жестких дисков и флэш-памяти
4.1. Контроллеры флэш-памяти, не обеспечивающие сохранение данных при отключении питания
4.2. USB-накопители с ложной емкостью
5. Повреждение памяти
6. Проблемы с другими операционными системами
6.1. Потоки Linux
6.2. Неисправности mmap() в QNX
6.3. Повреждение файловой системы
7. Ошибки конфигурации SQLite
8. Ошибки в SQLite
8.1. Ложные сообщения о повреждении из-за уменьшения размера базы данных
8.2. Повреждение после переключения между режимами отката и WAL
8.3. Ошибка ввода-вывода при получении блокировки приводит к повреждению
8.4. Страницы базы данных утекают из списка свободных страниц
8.5. Повреждение после чередующихся записей из 3.6 и 3.7
8.6. Состояние гонки при восстановлении в системах Windows
8.7. Ошибка граничного значения во вторичных журналах, используемых вложенными транзакциями

Обзор

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

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

1. Перезапись файла вредоносным потоком или процессом

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

1.1. Продолжение использования дескриптора файла после его закрытия

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

Один из примеров произошел около 2013-08-30 в репозитории Fossil DVCS. В этом случае дескриптор файла 2 (стандартный вывод ошибки) ошибочно закрывался (по нашему предположению, stunnel) до sqlite3_open_v2(), так что дескриптор файла, используемый для файла базы данных репозитория, был 2. Позже ошибка в приложении привела к тому, что утверждение assert() выводило сообщение об ошибке, вызывая write(2,...). Но так как дескриптор файла 2 был теперь подключен к файлу базы данных, сообщение об ошибке перезаписало часть базы данных. Чтобы защититься от этого рода проблем, SQLite версия 3.8.1 (2013-10-17) и более поздние версии отказываются использовать дескрипторы файлов с низкими номерами для файлов баз данных. (См. SQLITE_MINIMUM_FILE_DESCRIPTOR для дополнительной информации.)

Другой пример повреждения, вызванного использованием закрытого дескриптора файла, был сообщен инженерами Facebook в сообщении в блоге от 2014-08-12.

Еще один пример этой ошибки был сообщен в отношении Fossil 11 июля 2019 года. Дескриптор файла открывался для отладки, но затем закрывался и повторно открывался SQLite. Но логика отладки продолжала записывать в исходный дескриптор файла. См. обсуждение форума для отчета об ошибке и ссылки на исправление.

1.2. Архивирование или восстановление во время активной транзакции

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

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

  1. Утилита sqlite3_rsync (доступна начиная с SQLite 3.47.0 (2024-10-21) и более поздних версий) создаст копию живой базы данных SQLite по SSH с использованием протокола, эффективного с точки зрения пропускной способности.

  2. Команда VACUUM INTO filename копирует текущее состояние базы данных SQLite в отдельный файл.

  3. API резервного копирования - это интерфейс языка C, который может создавать согласованную копию базы данных SQLite.

Любой из вышеперечисленных методов будет работать даже на живой базе данных. Также безопасно создавать копию файла базы данных SQLite, если при этом не происходит никаких транзакций. Если предыдущая транзакция записи завершилась неудачно, необходимо скопировать журнал отката (файл *-journal) или журнал предварительной записи (файл *-wal) вместе с самим файлом базы данных.

1.3. Удаление активного журнала

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

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

Еще одно проявление этой проблемы - повреждение базы данных, вызванное несогласованным использованием 8+3 имен файлов.

1.4. Несоответствие файлов базы данных и активных журналов

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

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

2. Проблемы с блокировкой файлов

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

2.1. Файловые системы с неисправными или отсутствующими реализациями блокировки

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

2.2. Отмена блокировок POSIX по совету отдельным потоком, выполняющим close()

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

Например, предположим, что многопоточный процесс имеет два или более потока с отдельными подключениями к базе данных SQLite к одному и тому же файлу базы данных. Затем появляется третий поток, который хочет прочитать что-то из этого же файла базы данных самостоятельно, без использования библиотеки SQLite. Третий поток выполняет open(), read() и затем close(). Казалось бы, это безопасно. Но системный вызов close() привел к снятию всех блокировок, удерживаемых на базе данных всеми другими потоками. Эти другие потоки не имеют возможности узнать, что их блокировки были удалены (POSIX не предоставляет механизм для определения этого), и поэтому продолжают работать в предположении, что их блокировки все еще действительны. Это может привести к тому, что два или более потока или процесса будут пытаться записать в базу данных одновременно, что приведет к повреждению базы данных.

Обратите внимание, что для доступа к одному и тому же файлу базы данных SQLite несколькими потоками с использованием библиотеки SQLite вполне безопасно. Драйверы Unix для SQLite знают об особенностях консультативной блокировки POSIX и обходят их. Эта проблема возникает только тогда, когда поток пытается обойти библиотеку SQLite и прочитать файл базы данных напрямую.

2.2.1. Несколько копий SQLite, связанных с одним приложением

Как указывалось в предыдущем абзаце, SQLite предпринимает шаги для обхода особенностей консультативной блокировки POSIX. Часть этого обхода включает в себя поддержание глобального списка (защищенного мьютексом) открытых файлов базы данных SQLite. Но если в одно приложение связаны несколько копий SQLite, то будет несколько экземпляров этого глобального списка. Подключения к базе данных, открытые с помощью одной копии библиотеки SQLite, не будут знать о подключениях к базе данных, открытых с помощью другой копии, и не смогут обойти особенности консультативной блокировки POSIX. Операция close() в одном подключении может непреднамеренно очистить блокировки в другом подключении к базе данных, что приведет к повреждению базы данных.

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

2.3. Два процесса, использующих разные протоколы блокировки

По умолчанию механизм блокировки, используемый SQLite на платформах Unix, — это консультативная блокировка POSIX, но существуют и другие варианты. Выбрав альтернативный sqlite3_vfs с помощью интерфейса sqlite3_open_v2(), приложение может использовать другие протоколы блокировки, которые могут быть более подходящими для определенных файловых систем. Например, блокировка точечного файла может быть выбрана для использования в приложении, которое должно работать в файловой системе NFS, не поддерживающей консультативную блокировку POSIX.

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

2.4. Удаление или переименование файла базы данных во время использования

Если два процесса имеют открытые подключения к одному и тому же файлу базы данных, и один процесс закрывает свое подключение, удаляет файл, затем создаёт новый файл базы данных с тем же именем и повторно открывает новый файл, то два процесса будут общаться с разными файлами базы данных с тем же именем. (Обратите внимание, что это возможно только в системах Posix и подобных им, которые позволяют удалять файл, пока он всё ещё открыт для чтения и записи. Windows не позволяет этого.) Поскольку журналы отката и файлы WAL основаны на имени файла базы данных, два разных файла базы данных будут использовать один и тот же журнал отката или файл WAL. Откат или восстановление для одной из баз данных может использовать содержимое другой базы данных, что приведёт к повреждению. Аналогичная проблема возникает, если файл базы данных переименовывается во время открытия и создаётся новый файл со старым именем.

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

Начиная с SQLite версии 3.7.17 (2013-05-20), интерфейс Unix ОС отправляет сообщения SQLITE_WARNING в журнал ошибок, если файл базы данных удаляется, когда он всё ещё используется.

2.5. Несколько ссылок на один файл

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

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

Начиная с SQLite версии 3.7.17 (2013-05-20), интерфейс Unix ОС отправляет сообщения SQLITE_WARNING в журнал ошибок, если файл базы данных имеет несколько жестких ссылок.

Начиная с SQLite версии 3.10.0 (2016-01-06), интерфейс Unix ОС пытается разрешить символические ссылки и открыть файл базы данных по его каноническому имени. До версии 3.10.0 открытие файла базы данных через символическую ссылку было похоже на открытие файла базы данных с несколькими жесткими ссылками и приводило к неопределенному поведению.

2.6. Перенос открытого соединения с базой данных через fork()

Не открывайте подключение к базе данных SQLite, затем вызывайте fork(), а затем пытайтесь использовать это подключение к базе данных в дочернем процессе. Возникнут всевозможные проблемы с блокировкой, и вы легко можете получить повреждённую базу данных. SQLite не предназначен для поддержки такого поведения. Любое подключение к базе данных, используемое в дочернем процессе, должно быть открыто в дочернем процессе, а не унаследовано от родительского.

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

3. Неудачная синхронизация

Для гарантии целостности файлов базы данных SQLite время от времени запрашивает у операционной системы выполнить сброс всех ожидающих записей в постоянное хранилище и затем дождаться завершения этого сброса. Это достигается с помощью системного вызова fsync() в Unix и FlushFileBuffers() в Windows. Мы называем этот сброс ожидающих записей «синхронизацией».

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

3.1. Жёсткие диски, не выполняющие запросы на синхронизацию

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

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

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

3.2. Отключение синхронизации с помощью PRAGMA

Операции синхронизации, которые выполняет SQLite для обеспечения целостности, могут быть отключены во время выполнения с помощью pragma synchronous. Установив PRAGMA synchronous=OFF, все операции синхронизации пропускаются. Это позволяет SQLite работать быстрее, но также позволяет операционной системе свободно переупорядочивать записи, что может привести к повреждению базы данных, если произойдет отключение питания или перезагрузка жесткого диска до того, как все содержимое достигнет постоянного хранилища.

Для максимальной надежности и устойчивости к повреждению базы данных SQLite всегда следует запускать с параметром synchronous по умолчанию FULL.

4. Неисправности жестких дисков и флэш-памяти

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

4.1. Контроллеры флэш-памяти без защиты от сбоя питания

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

4.2. USB-накопители с ложной емкостью

В обращении находятся многочисленные поддельные USB-накопители, которые заявляют о высокой емкости (например, 8 ГБ), но на самом деле способны хранить гораздо меньший объем (например, 1 ГБ). Попытки записи на эти устройства часто приводят к перезаписи не связанных файлов. Любое использование поддельного устройства флэш-памяти может легко привести к повреждению базы данных. Поиск в интернете по запросу «USB-накопители с ложной емкостью» выявит много тревожной информации об этой проблеме.

5. Повреждение памяти

SQLite — это C-библиотека, которая выполняется в том же адресном пространстве, что и приложение, которому она служит. Это означает, что случайные указатели, переполнения буфера, повреждение кучи или другие сбои в приложении могут повредить внутренние структуры данных SQLite и, в конечном итоге, привести к повреждению файла базы данных. Обычно такие проблемы проявляются как segfaults до возникновения любого повреждения базы данных, но были случаи, когда ошибки в коде приложения приводили к тому, что SQLite работал некорректно, тем самым повреждая файл базы данных, а не аварийно завершая работу.

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

6. Другие проблемы операционной системы

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

6.1. Потоки Linux

В некоторых старых версиях Linux для поддержки потоков использовалась библиотека LinuxThreads. LinuxThreads похожа на Pthreads, но немного отличается с точки зрения обработки консультативных блокировок POSIX. Версии SQLite с 2.2.3 по 3.6.23 распознавали использование LinuxThreads во время выполнения и принимали соответствующие меры для обхода нестандартного поведения LinuxThreads. Но большинство современных реализаций Linux используют более новую и правильную реализацию NPTL для Pthreads. Начиная с SQLite версии 3.7.0 (2010-07-21), предполагается использование NPTL. Проверки не выполняются. Следовательно, последние версии SQLite могут работать некорректно и могут повредить файлы базы данных, если их использовать в многопоточном приложении, которое выполняется на более старых системах Linux, использующих LinuxThreads.

6.2. Ошибки mmap() в QNX

Существует некоторая тонкая проблема с mmap() в QNX, в результате которой вызов второй функции mmap() для одного дескриптора файла может привести к обнулению памяти, полученной из первого вызова mmap(). SQLite на Unix использует mmap() для создания области общей памяти для координации транзакций в режиме WAL, и она будет вызывать mmap() несколько раз для больших транзакций. Было продемонстрировано, что функция mmap() в QNX может повредить файл базы данных в этой ситуации. Инженеры QNX знают о этой проблеме и работают над решением; возможно, проблема уже была исправлена к моменту прочтения вами этого.

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

6.3. Повреждение файловой системы

Поскольку базы данных SQLite представляют собой обычные файлы на диске, любое неполадки в файловой системе могут повредить базу данных. Файловые системы современных операционных систем очень надежны, но ошибки все же случаются. Например, 2013-10-01 база данных SQLite, которая хранит вики Tcl/Tk, была повреждена через несколько дней после того, как компьютер был перенесен на сомнительную сборку ядра Linux, имевшую проблемы в слое файловой системы. В этом случае файловая система в конечном итоге стала настолько сильно поврежденной, что машина стала непригодной, но самым ранним признаком проблемы была поврежденная база данных SQLite.

7. Ошибки конфигурации SQLite

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

Ниже приведены примеры отключения встроенных механизмов защиты SQLite:

  • Установка PRAGMA synchronous=OFF может привести к повреждению базы данных, если произойдет сбой операционной системы или отключение питания, хотя этот параметр защищен от повреждений из-за сбоев приложения.

  • Изменение PRAGMA schema_version при открытых подключениях к базе данных.

  • Использование PRAGMA journal_mode=OFF или PRAGMA journal_mode=MEMORY и возникновение сбоя приложения в середине транзакции записи.

  • Установка PRAGMA writable_schema=ON и последующее изменение схемы базы данных с помощью операторов DML может сделать базу данных полностью нечитаемой, если это не делается аккуратно.

8. Ошибки в SQLite

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

Тем не менее, никакое программное обеспечение не является на 100% идеальным. Было несколько исторических ошибок в SQLite (теперь исправленных), которые могли привести к повреждению базы данных. И могут быть еще несколько, которые остаются невыявленными. Из-за обширного тестирования и широкого использования SQLite ошибки, приводящие к повреждению базы данных, как правило, являются очень скрытыми. Вероятность того, что приложение столкнется с ошибкой SQLite, невелика. В качестве иллюстрации ниже приводится описание всех ошибок, приведших к повреждению базы данных в SQLite в период с 2009-04-01 по 2013-04-15. Это описание должно дать читателю интуитивное представление о типах ошибок в SQLite, которые могут проскользнуть через процедуры тестирования и попасть в релиз.

8.1. Ложные сообщения о повреждении из-за уменьшения размера базы данных

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

Проблема была исправлена 2011-02-20. Исправление впервые появилось в SQLite версии 3.7.6 (2011-04-12).

8.2. Повреждение после переключения между режимами отката и WAL

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

Эта проблема была обнаружена во время внутренних тестов и никогда не наблюдалась в реальных условиях. Проблема была исправлена 2011-01-27 и в версии 3.7.5.

8.3. Ошибка ввода-вывода при получении блокировки приводит к повреждению

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

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

Эта проблема была исправлена 20.09.2010 для версии SQLite 3.7.3.

8.4. Утечка страниц базы данных из списка свободных страниц

Когда содержимое удаляется из базы данных SQLite, страницы, которые больше не используются, добавляются в список свободных страниц и повторно используются для хранения содержимого, добавляемого последующими операциями вставки. Баг в SQLite, присутствовавший в версиях с 3.6.16 по 3.7.2, мог привести к потере страниц из списка свободных страниц при использовании incremental_vacuum. Это не приведёт к потере данных. Но это приведёт к тому, что файл базы данных будет больше, чем необходимо. И это заставит pragma проверки целостности сообщить о недостающих страницах в списке свободных страниц.

Эта проблема была исправлена 23.08.2010 для версии SQLite 3.7.2.

8.5. Повреждение после чередующихся записей из 3.6 и 3.7

Версия SQLite 3.7.0 ввела ряд новых улучшений в формат файла базы данных SQLite (таких как, но не ограничиваясь, WAL). Релиз 3.7.0 был выпуском для отработки этих новых функций. Мы ожидали найти проблемы и не были разочарованы.

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

Эта проблема была исправлена 04.08.2010 для версии SQLite 3.7.1.

8.6. Гонка в процессе восстановления на системах Windows

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

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

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

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

В SQLite версии 3.35.0 (2021-03-12) была добавлена новая оптимизация, позволяющая использовать меньше оперативной памяти при хранении вторичных журналов в оперативной памяти. К сожалению, проверка границ в новой логике была закодирована неправильно. Оператор «<» был закодирован как «<=». Эта ошибка может привести к тому, что вторичный журнал перейдёт в несогласованное состояние, если он когда-либо будет откатён. Если будут внесены дополнительные изменения, и внешняя транзакция в конечном итоге завершится успешно, база данных может остаться в несогласованном состоянии.

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

Эта проблема была исправлена в версии 3.37.2 (2022-01-06).

Эта страница в последний раз была изменена 16.10.2024 11:05:34 UTC

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

Spec-Zone.ru

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