#android #c #sqlite #wal
Вопрос:
У меня есть приложение для Android, использующее SQLite через мост c (с использованием JNI/NDK). Я пытаюсь хранить данные в базе данных SQLite с включенным режимом WAL. Однако в большинстве случаев файл WAL либо отсутствует, либо пуст (размер 0 байт). Почему это может произойти?
Подробные сведения:
PRAGMA journal_mode=WAL;
- 1 подключение для чтения и записи (однако операции с БД — инициализация, вставка, выбор — выполняются в разных потоках. Однако все операции выполняются последовательно. Это не ситуация, когда одновременно работает более одного читателя/автора).
- Если я выполняю контрольную точку
PRAGMA wal_checkpoint;
, все транзакции, которые я вставил, переносятся из журнала в основной файл БД, даже если файл WAL визуально пуст/отсутствует. - Транзакции совершаются вручную с использованием
BEGIN TRANSACTION;
иEND TRANSACTION;
, без автоматической фиксации - Я проверяю состояние файла с помощью проводника файлов устройства в Android studio (также я пытался скопировать файлы журнала из внутреннего хранилища во внешнее).
- Файл БД и
-wal
/-shm
файлы находятся во внутренней памяти:/data/data/com.package.my/files/
- Транзакции фактически передаются в файл WAL — я проверил, настроив a
sqlite3_wal_hook
: он показывает, что количество страниц увеличивается - sqlite 3.27.2
- Android 7 (устройство Ciontek CS10)
- fs = ext4
- Версия NDK=21.4.7075529
Что я пробовал:
- Сериализованный потокобезопасный режим (
SQLITE_OPEN_FULLMUTEX
) PRAGMA locking_mode=EXCLUSIVE;
PRAGMA synchronous=EXTRA;
PRAGMA synchronous=FULL;
- установка VFS в
unix-excl
- изменение каталога файлов на /data/data/com.package.my/files/db (чтобы иметь папку только для файлов БД, так как /файлы содержат другие файлы)
- создание минимального воспроизводимого фрагмента. Часть проблемы заключается в том, что я попытался составить минимально воспроизводимый пример: сделал копию проекта только с частью БД (удалил все, кроме кода инициализации/DAO/сущности БД). И это свернутое приложение не смогло воспроизвести проблему: -файл wal всегда существует и не сжимается/не исчезает. Поэтому я в замешательстве, не могу понять, какая часть первоначального проекта вызывает проблемы.
Код:
int wal_hook(void* userdata, sqlite3* handle, const char* dbFilename, int nPages){
char* pChar;
pChar = (char*)userdata; // "test"
printf("Hello hook");
return SQLITE_OK;
}
// DB init (executed once on app start)
void initDB()
int32 rc = sqlite3_open(db_name.c_str(), amp;handle); // rc = 0
// int32 rc = sqlite3_open_v2(filename.c_str(), amp;handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr);
// int rc = sqlite3_open_v2(filename.c_str(), amp;handle, SQLITE_OPEN_READWRITE, "unix-excl");
// check threadsafe mode
int stResult = sqlite3_threadsafe(); // stResult = 1
// register WAL hook
char* pointerContext = new char[4]();
pointerContext[0] = 't';
pointerContext[1] = 'e';
pointerContext[2] = 's';
pointerContext[3] = 't';
sqlite3_wal_hook(handle, wal_hook, pointerContext);
// turn WAL mode on
int32 rcWAL = sqlite3_exec(handle, "PRAGMA journal_mode=WAL;", processResults, amp;result, amp;errMsg); // rcWAL = 0
}
// close connection
int32 close() {
return sqlite3_close(handle);
}
// WAL checkpoint
sqlite3_exec(handle, "pragma wal_checkpoint;", processResults, amp;result, amp;errMsg);
// Insert
EventPtr persist(EventPtr obj) {
vector<DBData*> args;
int beginResult = sqlite3_exec(_connector->handle, "BEGIN TRANSACTION;", NULL, NULL, NULL);
try {
args.push_back(amp;obj->eventId);
// ... push more args
string query = "insert into events values(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14);";
int32_t rc = _connector->executePreparedWOresult(query.c_str(),amp;args);
if(rc == SQLITE_DONE) {
int endResult = sqlite3_exec(_connector->handle, "END TRANSACTION;", NULL, NULL, NULL);
return obj;
}
} catch(...){ }
}
// SELECT
vector<EventPtr> readAll()
{
string query = "select * from Events;";
ResultSetPtr result= _connector->executePrepared(query.c_str(), NULL);
vector<EventPtr> vec;
for(int32_t i = 0; i < result->size(); i ){
EventPtr event(new Event);
// init event
vec.push_back(EventPtr(event));
}
return vec;
}
// executePreparedWOresult
int32 executePreparedWOresult(const stringamp; query, vector<DBData*> *args){
sqlite3_stmt *stmt;
cout << query ;
sqlite3_prepare_v2(handle, query.c_str(), query.size(), amp;stmt, NULL);
for(uint32 i = 0;args amp;amp; i < args->size(); i ){
switch(args->at(i)->getType()){
// statement bindings (sqlite3_bind_text, etc.)
}
}
int32 rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
return rc;
}
// executePrepared
ResultSetPtr executePrepared(const char *query, const vector<DBData*> *args){
ResultSetPtr res = ResultSetPtr(new ResultSet);
sqlite3_stmt *stmt;
int32_t rs = sqlite3_prepare_v2(handle, query, strlen(query), amp;stmt, NULL);
for(uint32 i = 0;args amp;amp; i < args->size(); i ){
switch(args->at(i)->getType()){
// statement bindings (sqlite3_bind_text, etc.)
}
}
int32 count = sqlite3_column_count(stmt);
int32 rc;
ResultRow row;
int32 rows = 0;
while((rc = sqlite3_step(stmt)) == SQLITE_ROW){
rows ;
for(int32 i = 0; i < count; i ){
// multiple row.push_back-s for all columns
}
res->push_back(row);
row.clear();
}
sqlite3_finalize(stmt);
return res;
}