Переход с SQLite 3.4.2 на 3.5.0
Версия SQLite 3.5.0 (2007-09-04) вводит новый слой интерфейса ОС, несовместимый со всеми предыдущими версиями SQLite. Кроме того, несколько существующих интерфейсов были обобщены для работы со всеми подключениями к базе данных в процессе, а не только со всеми подключениями в одном потоке. Цель этой статьи — подробно описать изменения в версии 3.5.0, чтобы пользователи предыдущих версий SQLite могли оценить необходимые усилия для обновления до новых версий.
1.0 Обзор изменений
Здесь приводится краткий перечень изменений в SQLite версии 3.5.0. В последующих разделах эти изменения будут описаны более подробно.
- Полностью переработан слой интерфейса ОС:
- Интерфейс sqlite3_os_switch() (недокументированный) удален.
- Флаг компиляции SQLITE_ENABLE_REDEF_IO больше не работает. Теперь процедуры ввода-вывода всегда могут быть переопределены.
- Для задания процедур ввода-вывода определены три новых объекта: sqlite3_vfs, sqlite3_file и sqlite3_io_methods.
- Три новых интерфейса используются для создания альтернативных интерфейсов ОС: sqlite3_vfs_register(), sqlite3_vfs_unregister() и sqlite3_vfs_find().
- Добавлен новый интерфейс для дополнительного управления созданием новых подключений к базе данных: sqlite3_open_v2(). Легасные интерфейсы sqlite3_open() и sqlite3_open16() по-прежнему полностью поддерживаются.
- Дополнительные функции кэширования и управления памятью, которые были введены в версии 3.3.0, теперь могут использоваться в нескольких потоках в одном процессе. Раньше эти расширения применялись только к подключениям к базе данных, работающим в одном потоке.
- Интерфейс sqlite3_enable_shared_cache() теперь применяется ко всем потокам в одном процессе, а не только к тому потоку, в котором он был запущен.
- Интерфейс sqlite3_soft_heap_limit() теперь применяется ко всем потокам в одном процессе, а не только к тому потоку, в котором он был запущен.
- Интерфейс sqlite3_release_memory() теперь попытается уменьшить использование памяти по всем подключениям к базе данных во всех потоках, а не только по подключениям в том потоке, где интерфейс вызывается.
- Интерфейс sqlite3_thread_cleanup() стал бесполезным.
- Ограничения на использование одного подключения к базе данных несколькими потоками были сняты. Теперь безопасно использовать одно подключение к базе данных в нескольких потоках одновременно.
- Теперь есть опция времени компиляции, которая позволяет приложению определять альтернативные реализации malloc()/free() без необходимости изменения кода ядра SQLite.
- Теперь есть опция времени компиляции, которая позволяет приложению определять альтернативные реализации mutex без необходимости изменения кода ядра SQLite.
Из этих изменений только 1a и 2a-2c являются несовместимостями в формальном смысле. Но пользователи, которые ранее внесли пользовательские изменения в исходный код SQLite (например, для добавления пользовательского слоя ОС для встроенного оборудования), могут обнаружить, что эти изменения окажут большее влияние. С другой стороны, важной целью этих изменений является упрощение настройки SQLite для использования на разных операционных системах.
2.0 Слой интерфейса ОС
Если ваша система определяет пользовательский интерфейс ОС для SQLite или вы использовали недокументированный интерфейс sqlite3_os_switch(), вам необходимо внести изменения для обновления до версии SQLite 3.5.0. Это может показаться сложным на первый взгляд. Но при более детальном рассмотрении вы, вероятно, обнаружите, что ваши изменения становятся более компактными, понятными и управляемыми благодаря новому интерфейсу SQLite. Вероятно, ваши изменения теперь также будут беспрепятственно работать с объединением SQLite. Вам больше не нужно вносить какие-либо изменения в исходный код SQLite. Все ваши изменения могут быть внесены кодом приложения, и вы можете подключаться к стандартной, неизменённой версии объединения SQLite. Кроме того, слой интерфейса ОС, который ранее не был документирован, теперь является официально поддерживаемым интерфейсом для SQLite. Поэтому у вас есть уверенность, что это будет однократное изменение, и ваш новый бэкенд будет продолжать работать в будущих версиях SQLite.
2.1 Объект виртуальной файловой системы
Новый интерфейс ОС для SQLite построен вокруг объекта с именем sqlite3_vfs. Аббревиатура "vfs" означает "виртуальная файловая система". Объект sqlite3_vfs по сути является структурой, содержащей указатели на функции, которые реализуют основные операции ввода-вывода на диске, которые SQLite должна выполнять для чтения и записи баз данных. В этой статье мы часто будем ссылаться на объекты sqlite3_vfs как на "VFS".
SQLite может одновременно использовать несколько VFS. Каждое отдельное подключение к базе данных связано только с одной VFS. Но если у вас несколько подключений к базе данных, каждое подключение может быть связано с другой VFS.
Всегда существует стандартная VFS. Легасные интерфейсы sqlite3_open() и sqlite3_open16() всегда используют стандартную VFS.
Новый интерфейс создания подключений к базе данных sqlite3_open_v2() позволяет указать используемую VFS по имени.
2.1.1 Регистрация новых объектов VFS
Стандартные сборки SQLite для Unix или Windows поставляются с одной VFS с именем "unix" или "win32" соответственно. Эта VFS также является стандартной. Так что если вы используете старые функции открытия, всё будет работать так же, как и прежде. Изменение заключается в том, что приложение теперь может добавлять новые модули VFS для реализации пользовательского слоя ОС. API sqlite3_vfs_register() может использоваться для того, чтобы сообщить SQLite об одном или нескольких модулях VFS, определённых приложением:
int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt);
Приложения могут вызывать sqlite3_vfs_register() в любое время, хотя, конечно, VFS должна быть зарегистрирована, прежде чем она сможет использоваться. Первый аргумент — указатель на настраиваемый объект VFS, подготовленный приложением. Второй аргумент — true, чтобы сделать новую VFS стандартной, так что она будет использоваться старыми API sqlite3_open() и sqlite3_open16(). Если новая VFS не является стандартной, то, вероятно, вам придётся использовать новый API sqlite3_open_v2() для её использования. Однако, если новая VFS является единственной известной SQLite (если SQLite скомпилирована без стандартной VFS или если предварительно скомпилированная стандартная VFS была удалена с помощью sqlite3_vfs_unregister()), то новая VFS автоматически становится стандартной VFS независимо от значения аргумента makeDflt в sqlite3_vfs_register().
Стандартные сборки включают стандартные VFS "unix" или "win32". Но если вы используете опцию компиляции -DOS_OTHER=1, то SQLite собирается без стандартной VFS. В этом случае приложение должно зарегистрировать хотя бы одну VFS перед вызовом sqlite3_open(). Этот подход следует использовать встроенным приложениям. Вместо модификации исходного кода SQLite для вставки альтернативного слоя ОС, как это делалось в предыдущих выпусках SQLite, следует скомпилировать неизменённый исходный файл SQLite (предпочтительно объединение) с опцией -DOS_OTHER=1, а затем вызвать sqlite3_vfs_register() для определения интерфейса к базовой файловой системе перед созданием каких-либо подключений к базе данных.
2.1.2 Дополнительный контроль над объектами VFS
API sqlite3_vfs_unregister() используется для удаления существующей VFS из системы.
int sqlite3_vfs_unregister(sqlite3_vfs*);
API sqlite3_vfs_find() используется для поиска определённой VFS по имени. Вот его прототип:
sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName);
Аргументом является символическое имя желаемой VFS. Если аргумент является указателем NULL, то возвращается стандартная VFS. Функция возвращает указатель на объект sqlite3_vfs, реализующий VFS. Либо возвращает указатель NULL, если не был найден соответствующий объект.
2.1.3 Модификации существующих VFS
После регистрации VFS её не следует модифицировать. Если требуется изменение поведения, следует зарегистрировать новую VFS. Приложение может использовать sqlite3_vfs_find() для поиска старой VFS, скопировать старую VFS в новый объект sqlite3_vfs, внести необходимые изменения в новую VFS, отменить регистрацию старой VFS и затем зарегистрировать новую VFS на её месте. Существующие подключения к базе данных будут продолжать использовать старую VFS даже после её отмены регистрации, но новые подключения к базе данных будут использовать новую VFS.
2.1.4 Объект VFS
Объект VFS является экземпляром следующей структуры:
typedef struct sqlite3_vfs sqlite3_vfs;
struct sqlite3_vfs {
int iVersion; /* Structure version number */
int szOsFile; /* Size of subclassed sqlite3_file */
int mxPathname; /* Maximum file pathname length */
sqlite3_vfs *pNext; /* Next registered VFS */
const char *zName; /* Name of this virtual file system */
void *pAppData; /* Pointer to application-specific data */
int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*,
int flags, int *pOutFlags);
int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
int (*xAccess)(sqlite3_vfs*, const char *zName, int flags);
int (*xGetTempName)(sqlite3_vfs*, char *zOut);
int (*xFullPathname)(sqlite3_vfs*, const char *zName, char *zOut);
void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
void *(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol);
void (*xDlClose)(sqlite3_vfs*, void*);
int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
int (*xSleep)(sqlite3_vfs*, int microseconds);
int (*xCurrentTime)(sqlite3_vfs*, double*);
/* New fields may be appended in figure versions. The iVersion
** value will increment whenever this happens. */
};
Для создания новой VFS приложение заполняет экземпляр этой структуры соответствующими значениями и затем вызывает sqlite3_vfs_register().
Поле iVersion объекта sqlite3_vfs должно быть равно 1 для SQLite версии 3.5.0. Это число может увеличиться в будущих версиях SQLite, если нам потребуется изменить объект VFS каким-либо образом. Надеемся, что этого никогда не произойдёт, но такая возможность предусмотрена на случай необходимости.
Поле szOsFile содержит размер структуры, определяющей открытый файл: объект sqlite3_file. Этот объект будет описан более подробно ниже. Суть в том, что каждая реализация VFS может определить свой собственный объект sqlite3_file, содержащий любую информацию, необходимую для хранения реализацией VFS об открытом файле. Однако SQLite необходимо знать размер этого объекта, чтобы предварительно выделить достаточно места для него.
Поле mxPathname — максимальная длина имени пути к файлу, которую может использовать эта VFS. SQLite иногда вынуждено предварительно выделять буферы этого размера, поэтому оно должно быть как можно меньше. Некоторые файловые системы допускают очень длинные пути, но на практике пути редко превышают 100 байт или около того. Вам не нужно указывать наибольшую длину пути, которую может обработать базовая файловая система. Вам нужно указать только наибольшую длину пути, которую вы хотите, чтобы SQLite мог обработать. Несколько сотен — хорошее значение в большинстве случаев.
Поле pNext используется внутри SQLite. В частности, SQLite использует это поле для формирования связанного списка зарегистрированных VFS.
Поле zName — символическое имя VFS. Это имя, с которым сравнивает sqlite3_vfs_find(), когда ищет VFS.
Указатель pAppData не используется ядром SQLite. Этот указатель доступен для хранения вспомогательной информации, которую VFS может хотеть хранить.
Остальные поля объекта sqlite3_vfs хранят указатели на функции, реализующие базовые операции. Мы называем их "методами". Первый метод, xOpen, используется для открытия файлов на базовом носителе данных. Результатом является объект sqlite3_file. Существуют дополнительные методы, определённые самим объектом sqlite3_file, которые используются для чтения, записи и закрытия файла. Дополнительные методы подробно описаны ниже. Имя файла представлено в кодировке UTF-8. SQLite гарантирует, что строка zFilename, переданная в xOpen(), является полным путем, как сгенерированным xFullPathname(), и что строка будет валидной и неизменной до вызова xClose(). Таким образом, sqlite3_file может хранить указатель на имя файла, если ему нужно запомнить имя файла по какой-либо причине. Аргумент flags в xOpen() является копией аргумента flags в sqlite3_open_v2(). Если используется sqlite3_open() или sqlite3_open16(), то flags — это SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE. Если xOpen() открывает файл только для чтения, то он устанавливает *pOutFlags, включая SQLITE_OPEN_READONLY. Другие биты в *pOutFlags могут быть установлены. SQLite также добавит один из следующих флагов к вызову xOpen(), в зависимости от открываемого объекта:
- SQLITE_OPEN_MAIN_DB
- SQLITE_OPEN_MAIN_JOURNAL
- SQLITE_OPEN_TEMP_DB
- SQLITE_OPEN_TEMP_JOURNAL
- SQLITE_OPEN_TRANSIENT_DB
- SQLITE_OPEN_SUBJOURNAL
- SQLITE_OPEN_SUPER_JOURNAL
Разница между базой данных SQLITE_OPEN_TEMP_DB и базой данных SQLITE_OPEN_TRANSIENT_DB заключается в следующем: база данных SQLITE_OPEN_TEMP_DB используется для явно объявленных и именованных временных таблиц (используя синтаксис CREATE TEMP TABLE) или для именованных таблиц во временной базе данных, которая создаётся путём открытия базы данных с пустым именем файла. SQLITE_OPEN_TRANSIENT_DB хранит таблицу базы данных, которую SQLite создаёт автоматически для оценки подзапроса или операторов ORDER BY или GROUP BY. Обе базы данных TEMP_DB и TRANSIENT_DB являются закрытыми и удаляются автоматически. Базы данных TEMP_DB существуют на протяжении всего подключения к базе данных. Базы данных TRANSIENT_DB существуют только на протяжении одного SQL-запроса.
Метод xDelete используется для удаления файла. Имя файла указано во втором параметре. Имя файла будет в кодировке UTF-8. VFS должен преобразовать имя файла в представление символов, ожидаемое базовой операционной системой. Если параметр syncDir имеет значение true, то метод xDelete не должен возвращаться до тех пор, пока изменения в содержимом каталога, содержащего удалённый файл, не будут синхронизированы с диском, чтобы гарантировать, что файл не "появится снова", если вскоре после этого произойдёт отключение питания.
Метод xAccess используется для проверки разрешений доступа к файлу. Имя файла будет закодировано в UTF-8. Аргумент flags будет SQLITE_ACCESS_EXISTS для проверки наличия файла, SQLITE_ACCESS_READWRITE для проверки того, является ли файл как читаемым, так и записываемым, или SQLITE_ACCESS_READ для проверки того, является ли файл хотя бы читаемым. Названный "файлом" второй параметр может быть именем каталога или папки.
Метод xGetTempName вычисляет имя временного файла, который может использовать SQLite. Имя должно быть записано в буфер, заданный вторым параметром. SQLite будет задавать размер этого буфера, чтобы он мог вместить не менее mxPathname байт. Сгенерированное имя файла должно быть в UTF-8. Чтобы избежать проблем с безопасностью, сгенерированное временное имя файла должно содержать достаточно случайности, чтобы предотвратить угадывание временного имени файла атакующим заранее.
Метод xFullPathname используется для преобразования относительного пути в полный путь. Полученный полный путь записывается в буфер, предоставленный третьим параметром. SQLite будет задавать размер буфера вывода, чтобы он мог вместить не менее mxPathname байт. И входные, и выходные имена должны быть в UTF-8.
Методы xDlOpen, xDlError, xDlSym и xDlClose используются для доступа к динамическим библиотекам во время выполнения. Эти методы могут быть пропущены (и их указатели установлены в ноль), если библиотека скомпилирована с SQLITE_OMIT_LOAD_EXTENSION или если интерфейс sqlite3_enable_load_extension() никогда не использовался для включения загрузки динамических расширений. Метод xDlOpen открывает динамическую библиотеку или DLL и возвращает указатель на дескриптор. Возвращается NULL, если открытие не удалось. Если открытие не удалось, метод xDlError может быть использован для получения текстового сообщения об ошибке. Сообщение записывается в буфер zErrMsg третьего параметра, длина которого составляет не менее nByte байт. xDlSym возвращает указатель на символ в динамической библиотеке. Имя символа задаётся вторым параметром. Предполагается кодировка UTF-8. Если символ не найден, возвращается нулевой указатель. Процедура xDlClose закрывает динамическую библиотеку.
Метод xRandomness используется ровно один раз для инициализации генератора псевдослучайных чисел (PRNG) внутри SQLite. Только метод xRandomness в стандартном VFS используется. Методы xRandomness в других VFS никогда не вызываются SQLite. Процедура xRandomness запрашивает запись nByte байт случайности в zOut. Процедура возвращает фактическое количество полученных байт случайности. Качество полученной случайности определит качество случайности, генерируемой встроенными функциями SQLite, такими как random() и randomblob(). SQLite также использует свой PRNG для генерации временных имён файлов. На некоторых платформах (например, Windows) SQLite предполагает, что имена временных файлов уникальны, не проверяя фактически коллизии, поэтому важно иметь качественную случайность, даже если функции random() и randomblob() никогда не используются.
Метод xSleep используется для приостановки потока вызова на время не менее заданного количества микросекунд. Этот метод используется для реализации API sqlite3_sleep() и sqlite3_busy_timeout(). В случае sqlite3_sleep() метод xSleep стандартного VFS всегда используется. Если базовая система не поддерживает микросекундную точность приостановки, то время приостановки должно быть округлено вверх. xSleep возвращает это округлённое значение.
Метод xCurrentTime находит текущее время и дату и записывает результат как число с плавающей запятой двойной точности в указатель, предоставленный вторым параметром. Время и дата находятся в координированном универсальном времени (UTC) и представляют собой дробное число юлианских дней.
2.1.5 Объект открытого файла
Результатом открытия файла является экземпляр объекта sqlite3_file. Объект sqlite3_file — это абстрактный базовый класс, определённый следующим образом:
typedef struct sqlite3_file sqlite3_file;
struct sqlite3_file {
const struct sqlite3_io_methods *pMethods;
};
Каждая реализация VFS будет расширять sqlite3_file, добавляя дополнительные поля в конце, чтобы хранить любую информацию, необходимую VFS для открытого файла. Неважно, какая информация хранится, пока общий размер структуры не превышает значение szOsFile, записанное в объекте sqlite3_vfs.
Объект sqlite3_io_methods — это структура, содержащая указатели на методы для чтения, записи и прочей обработки файлов. Этот объект определён следующим образом:
typedef struct sqlite3_io_methods sqlite3_io_methods;
struct sqlite3_io_methods {
int iVersion;
int (*xClose)(sqlite3_file*);
int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst);
int (*xTruncate)(sqlite3_file*, sqlite3_int64 size);
int (*xSync)(sqlite3_file*, int flags);
int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize);
int (*xLock)(sqlite3_file*, int);
int (*xUnlock)(sqlite3_file*, int);
int (*xCheckReservedLock)(sqlite3_file*);
int (*xFileControl)(sqlite3_file*, int op, void *pArg);
int (*xSectorSize)(sqlite3_file*);
int (*xDeviceCharacteristics)(sqlite3_file*);
/* Additional methods may be added in future releases */
};
Поле iVersion объекта sqlite3_io_methods предоставлено на случай будущих улучшений. Значение iVersion всегда должно быть 1 для SQLite версии 3.5.
Метод xClose закрывает файл. Память для структуры sqlite3_file освобождается вызывающим методом. Но если sqlite3_file содержит указатели на другую выделенную память или ресурсы, эти выделения должны быть освобождены методом xClose.
Метод xRead считывает iAmt байт из файла, начиная с байтового смещения iOfst. Считанные данные хранятся в указателе второго параметра. xRead возвращает SQLITE_OK при успехе, SQLITE_IOERR_SHORT_READ, если не удалось прочитать все байты из-за достижения конца файла, или SQLITE_IOERR_READ при любой другой ошибке.
Метод xWrite записывает iAmt байт данных из второго параметра в файл, начиная со смещения iOfst байт. Если размер файла меньше iOfst байт до записи, то xWrite должен гарантировать расширение файла нулями до iOfst байт перед началом записи. xWrite продолжает расширять файл по мере необходимости, чтобы размер файла составлял не менее iAmt+iOfst байт по завершении вызова xWrite. Метод xWrite возвращает SQLITE_OK при успехе. Если запись не может быть завершена из-за заполненности базового носителя данных, возвращается SQLITE_FULL. SQLITE_IOERR_WRITE должен быть возвращён при любой другой ошибке.
Метод xTruncate обрезает файл до длины nByte байт. Если размер файла уже nByte байт или меньше, этот метод не выполняется. Метод xTruncate возвращает SQLITE_OK при успехе и SQLITE_IOERR_TRUNCATE при любой проблеме.
Метод xSync используется для принудительной выгрузки ранее записанных данных из кэша операционной системы в энергонезависимую память. Второй параметр обычно равен SQLITE_SYNC_NORMAL. Если второй параметр равен SQLITE_SYNC_FULL, то метод xSync должен гарантировать, что данные также были сброшены в кэш контроллеров диска. Параметр SQLITE_SYNC_FULL эквивалентен ioctl() F_FULLSYNC в Mac OS X. Метод xSync возвращает SQLITE_OK при успехе и SQLITE_IOERR_FSYNC в случае возникновения проблем.
Метод xFileSize() определяет текущий размер файла в байтах и записывает это значение в *pSize. Он возвращает SQLITE_OK при успехе и SQLITE_IOERR_FSTAT при возникновении проблем.
Методы xLock и xUnlock используются для установки и сброса блокировок файлов. SQLite поддерживает пять уровней блокировок файлов в порядке:
Базовая реализация может поддерживать некоторое подмножество этих уровней блокировки, если это соответствует другим требованиям данного абзаца. Уровень блокировки задается как второй аргумент для xLock и xUnlock. Метод xLock увеличивает уровень блокировки до указанного уровня или выше. Метод xUnlock уменьшает уровень блокировки до уровня не ниже указанного. SQLITE_LOCK_NONE означает, что файл разблокирован. SQLITE_LOCK_SHARED предоставляет разрешение на чтение файла. Несколько подключений к базе данных могут одновременно удерживать SQLITE_LOCK_SHARED. SQLITE_LOCK_RESERVED аналогично SQLITE_LOCK_SHARED в том, что предоставляет разрешение на чтение файла. Но только одно подключение может удерживать блокировку с отведением в любой момент времени. SQLITE_LOCK_PENDING также предоставляет разрешение на чтение файла. Другие подключения могут продолжать читать файл, но никакое другое подключение не может повысить уровень блокировки с «нет» до «совместной». SQLITE_LOCK_EXCLUSIVE предоставляет разрешение на запись в файл. Только одно подключение может удерживать эксклюзивную блокировку, и никакое другое подключение не может удерживать никакую блокировку (кроме «нет»), пока одно подключение удерживает эксклюзивную блокировку. Метод xLock возвращает SQLITE_OK при успехе, SQLITE_BUSY, если невозможно получить блокировку, или SQLITE_IOERR_RDLOCK при других проблемах. Метод xUnlock возвращает SQLITE_OK при успехе и SQLITE_IOERR_UNLOCK при проблемах.Метод xCheckReservedLock() проверяет, удерживает ли другое подключение или другой процесс в данный момент зарезервированную, ожидающую или эксклюзивную блокировку файла. Он возвращает true или false.
Метод xFileControl() — это универсальный интерфейс, позволяющий пользовательским реализациям VFS напрямую управлять открытым файлом с использованием интерфейса sqlite3_file_control() (новый и экспериментальный). Второй аргумент «op» — целочисленный код операции. Третий аргумент — универсальный указатель, который, как предполагается, является указателем на структуру, которая может содержать аргументы или место для записи возвращаемых значений. Возможные применения xFileControl() — функции для включения блокировок с тайм-аутами, изменения стратегии блокировки (например, использование блокировок с точкой), запроса статуса блокировки или разрыва устаревших блокировок. Ядро SQLite резервирует коды операций меньше 100 для собственного использования. Доступен список кодов операций меньше 100. Приложения, определяющие пользовательский метод xFileControl, должны использовать коды операций больше 100, чтобы избежать конфликтов.
Метод xSectorSize возвращает «размер сектора» базового энергонезависимого носителя. «Сектор» определяется как наименьшая единица хранения, которая может быть записана без нарушения соседних областей хранения. На жестком диске «размер сектора» до недавнего времени составлял 512 байт, хотя существует тенденция увеличить это значение до 4 Кбайт. SQLite необходимо знать размер сектора, чтобы записывать целые сектора за раз и тем самым избегать повреждения соседних областей хранения в случае отключения питания во время записи.
Метод xDeviceCharacteristics возвращает целочисленную битовую маску, определяющую любые специальные свойства базового хранилища, которые SQLite может использовать для повышения производительности. Разрешенное значение — это побитовое ИЛИ следующих значений:
- SQLITE_IOCAP_ATOMIC
- SQLITE_IOCAP_ATOMIC512
- SQLITE_IOCAP_ATOMIC1K
- SQLITE_IOCAP_ATOMIC2K
- SQLITE_IOCAP_ATOMIC4K
- SQLITE_IOCAP_ATOMIC8K
- SQLITE_IOCAP_ATOMIC16K
- SQLITE_IOCAP_ATOMIC32K
- SQLITE_IOCAP_ATOMIC64K
- SQLITE_IOCAP_SAFE_APPEND
- SQLITE_IOCAP_SEQUENTIAL
2.1.6 Список проверок для создания новой VFS
Предыдущие абзацы содержат много информации. Для облегчения задачи создания новой VFS для SQLite мы предлагаем следующий контрольный список реализации:
- Определите соответствующий подкласс объекта sqlite3_file.
- Реализуйте методы, необходимые для объекта sqlite3_io_methods.
- Создайте статический и постоянный объект sqlite3_io_methods, содержащий указатели на методы из предыдущего шага.
- Реализуйте метод xOpen, который открывает файл и заполняет объект sqlite3_file, включая установку pMethods для указания на объект sqlite3_io_methods с предыдущего шага.
- Реализуйте другие методы, необходимые для sqlite3_vfs.
- Определите статическую (но не постоянную) структуру sqlite3_vfs, которая содержит указатели на метод xOpen и другие методы, а также соответствующие значения для iVersion, szOsFile, mxPathname, zName и pAppData.
- Реализуйте процедуру, которая вызывает sqlite3_vfs_register() и передает ей указатель на структуру sqlite3_vfs с предыдущего шага. Эта процедура, вероятно, является единственным экспортируемым символом в файле исходного кода, который реализует вашу VFS.
В приложении вызовите процедуру, реализованную на последнем шаге выше, в качестве части процесса инициализации до открытия любых подключений к базе данных.
3.0 Подсистема выделения памяти
Начиная с версии 3.5, SQLite получает всю необходимую память кучи, используя процедуры sqlite3_malloc(), sqlite3_free() и sqlite3_realloc(). Эти процедуры существовали и в предыдущих версиях SQLite, но ранее SQLite обходил эти процедуры и использовал собственный выделенный менеджер памяти. Все меняется в версии 3.5.0.
Фактически в дереве исходного кода SQLite есть несколько версий менеджера памяти. По умолчанию используется высокоскоростная версия из файла «mem1.c», используемая для большинства сборок. Но если флаг SQLITE_MEMDEBUG включён, используется отдельный менеджер памяти из файла «mem2.c». Менеджер mem2.c реализует множество хуков для проверки ошибок и имитации сбоев выделения памяти для целей тестирования. Оба этих выделения используют реализацию malloc()/free() в стандартной библиотеке C.
Приложения не обязаны использовать ни один из этих стандартных менеджеров памяти. Если SQLite скомпилирован с SQLITE_OMIT_MEMORY_ALLOCATION, то не предоставляется никакой реализации функций sqlite3_malloc(), sqlite3_realloc() и sqlite3_free(). Вместо этого приложение, связанное с SQLite, должно предоставить собственную реализацию этих функций. Необязательно, чтобы реализация памяти приложения использовала реализацию malloc()/free() в стандартной библиотеке C. Например, встроенное приложение может предоставить альтернативный менеджер памяти, который использует память фиксированного пула, отведённого для эксклюзивного использования SQLite.
Приложения, которые реализуют собственный менеджер памяти, должны предоставить реализацию обычных трёх функций выделения: sqlite3_malloc(), sqlite3_realloc() и sqlite3_free(). И они также должны реализовать четвёртую функцию:
int sqlite3_memory_alarm( void(*xCallback)(void *pArg, sqlite3_int64 used, int N), void *pArg, sqlite3_int64 iThreshold );
Процедура sqlite3_memory_alarm используется для регистрации обратного вызова при событиях выделения памяти. Эта процедура регистрирует или очищает обратный вызов, который срабатывает, когда объём выделенной памяти превышает iThreshold. Одновременно может быть зарегистрирован только один обратный вызов. Каждый вызов sqlite3_memory_alarm() перезаписывает предыдущий обратный вызов. Обратный вызов отключается путём установки xCallback в NULL-указатель.
Параметрами обратного вызова являются значение pArg, объём памяти, который в настоящее время используется, и размер выделения, которое спровоцировало обратный вызов. Обратный вызов, по всей видимости, вызовет sqlite3_free() для освобождения места в памяти. Обратный вызов может вызвать sqlite3_malloc() или sqlite3_realloc(), но если он это сделает, никакие дополнительные обратные вызовы не будут вызваны рекурсивными вызовами.
Интерфейс sqlite3_soft_heap_limit() работает путём регистрации сигнала о выделении памяти на уровне мягкого лимита кучи и вызова sqlite3_release_memory() в обработчике сигнала. Программы приложений не должны пытаться использовать интерфейс sqlite3_memory_alarm(), так как это повлияет на модуль sqlite3_soft_heap_limit(). Этот интерфейс показан только для того, чтобы приложения могли предоставить собственную альтернативную реализацию, когда ядро SQLite скомпилировано с SQLITE_OMIT_MEMORY_ALLOCATION.
Встроенные механизмы выделения памяти в SQLite также предоставляют следующие дополнительные интерфейсы:
sqlite3_int64 sqlite3_memory_used(void); sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
Эти интерфейсы могут быть использованы приложением для мониторинга того, сколько памяти использует SQLite. Функция sqlite3_memory_used() возвращает количество байтов памяти, используемой в настоящее время, а функция sqlite3_memory_highwater() возвращает максимальное мгновенное использование памяти. Ни одна из функций не включает накладные расходы, связанные с выделением памяти. Эти функции предоставляются для использования приложением. SQLite никогда не вызывает их сам. Таким образом, если приложение предоставляет свою собственную систему выделения памяти, оно может опустить эти интерфейсы по желанию.
4.0 Подсистема мьютексов
SQLite всегда был потокобезопасным в том смысле, что можно использовать разные подключения к базе данных SQLite в разных потоках одновременно. Ограничение заключалось в том, что одно и то же подключение к базе данных не могло использоваться в двух отдельных потоках одновременно. Версия SQLite 3.5.0 снимает это ограничение.
Для того, чтобы разрешить нескольким потокам использовать одно и то же подключение к базе данных одновременно, SQLite должно широко использовать мьютексы. И по этой причине была добавлена новая подсистема мьютексов. Подсистема мьютексов имеет следующий интерфейс:
sqlite3_mutex *sqlite3_mutex_alloc(int); void sqlite3_mutex_free(sqlite3_mutex*); void sqlite3_mutex_enter(sqlite3_mutex*); int sqlite3_mutex_try(sqlite3_mutex*); void sqlite3_mutex_leave(sqlite3_mutex*);
Хотя эти функции существуют для использования ядром SQLite, код приложения также может свободно использовать эти функции, если это необходимо. Мьютекс — это объект sqlite3_mutex. Функция sqlite3_mutex_alloc() выделяет новый объект мьютекса и возвращает указатель на него. Аргументом для sqlite3_mutex_alloc() должен быть SQLITE_MUTEX_FAST или SQLITE_MUTEX_RECURSIVE для нерекурсивных и рекурсивных мьютексов соответственно. Если основная система не предоставляет нерекурсивные мьютексы, то в этом случае можно использовать рекурсивный мьютекс. Аргументом для sqlite3_mutex_alloc() также может быть константа, обозначающая один из нескольких статических мьютексов:
- SQLITE_MUTEX_STATIC_MAIN
- SQLITE_MUTEX_STATIC_MEM
- SQLITE_MUTEX_STATIC_MEM2
- SQLITE_MUTEX_STATIC_PRNG
- SQLITE_MUTEX_STATIC_LRU
Для освобождения нестатического мьютекса следует использовать функцию sqlite3_mutex_free(). Если в эту функцию передаётся статический мьютекс, то поведение не определено.
Функция sqlite3_mutex_enter() пытается войти в мьютекс и блокируется, если в нём уже находится другой поток. sqlite3_mutex_try() пытается войти и возвращает SQLITE_OK при успехе или SQLITE_BUSY, если в нём уже находится другой поток. sqlite3_mutex_leave() выходит из мьютекса. Мьютекс удерживается до тех пор, пока количество выходов не совпадёт с количеством входов. Если sqlite3_mutex_leave() вызывается для мьютекса, которого текущий поток не держит, то поведение не определено. Если любая функция вызывается для освобождённого мьютекса, то поведение не определено.
Исходный код SQLite предоставляет несколько реализаций этих API, подходящих для различных сред. Если SQLite скомпилирован со флагом SQLITE_THREADSAFE=0, то предоставляется реализация мьютекса, которая является быстрой, но не обеспечивает реального взаимного исключения. Эта реализация подходит для использования в однопоточных приложениях или приложениях, которые используют SQLite только в одном потоке. Другие реальные реализации мьютексов предоставляются на основе операционной системы.
Приложения могут захотеть предоставить свою собственную реализацию мьютекса. Если SQLite скомпилирован с флагом -DSQLITE_MUTEX_APPDEF=1, то ядро SQLite не предоставляет подсистему мьютексов, и подсистему мьютексов, соответствующую описанному выше интерфейсу, должен предоставить приложение, которое подключается к SQLite.
5.0 Другие изменения интерфейса
Версия 3.5.0 SQLite изменяет поведение нескольких API способами, которые технически несовместимы. Однако эти API используются редко, и даже когда они используются, трудно представить сценарий, в котором изменение может что-то сломать. Изменения фактически делают эти интерфейсы намного более полезными и мощными.
До версии 3.5.0 API sqlite3_enable_shared_cache() включал и выключал функцию кэширования для всех подключений в одном потоке — том же потоке, из которого вызывалась функция sqlite3_enable_shared_cache(). Подключения к базе данных, использующие кэширование, были ограничены выполнением в том же потоке, в котором они были открыты. Начиная с версии 3.5.0, sqlite3_enable_shared_cache() применяется ко всем подключениям к базам данных во всех потоках в пределах процесса. Теперь подключения к базам данных, работающие в отдельных потоках, могут использовать общий кэш. И подключения к базам данных, использующие общий кэш, могут переходить из одного потока в другой.
До версии 3.5.0 функция sqlite3_soft_heap_limit() устанавливала верхнюю границу использования памяти кучи для всех подключений к базе данных в пределах одного потока. Каждый поток мог иметь свой предел кучи. Начиная с версии 3.5.0, существует один предел кучи для всего процесса. Это кажется более жёстким (один предел вместо многих), но на практике именно это хотят большинство пользователей.
До версии 3.5.0 функция sqlite3_release_memory() пыталась вернуть память из всех подключений к базам данных в том же потоке, что и вызов sqlite3_release_memory(). Начиная с версии 3.5.0, функция sqlite3_release_memory() будет пытаться вернуть память из всех подключений к базам данных во всех потоках.
6.0 Сводка
Переход от версии SQLite 3.4.2 к 3.5.0 — это значительное изменение. Каждый файл исходного кода в ядре SQLite пришлось изменить, некоторые — существенно. И это изменение привело к некоторым незначительным несовместимостям в интерфейсе C. Но мы считаем, что преимущества перехода с 3.4.2 на 3.5.0 значительно перевешивают трудности переноса. Новый слой VFS теперь хорошо определён и стабилен и должен упростить будущие кастомизации. Слой VFS и отдельные подсистемы выделения памяти и мьютексов позволяют использовать стандартное объединение исходного кода SQLite в проект встраиваемой системы без изменений, значительно упрощая управление конфигурацией. И получившаяся система гораздо более устойчива к высокопоточным проектам.
Эта страница была последним изменена 08.01.2022 05:02:57 UTC
SQLite is in the Public Domain.
https://sqlite.org/34to35.html