当连接未正确关闭时,为什么在启用 WAL 模式的 SQLite 数据库中数据丢失?
Why data is lost in SQLite database with WAL mode on when connection is not closed properly?
问题:如果先前的连接未正确关闭,则无法使用 WAL 模式(预写日志)从新连接到 SQLite 数据库 SELECT
获取数据。主要问题是:为什么数据会丢失,有什么方法可以让丢失的交易消失吗?
我正在尝试将数据存储在 SQLite table 中,并启用了 WAL mode
。我将描述 3 种情况:情况 A 导致交易损失,情况 B 和 C - 不会。
情况A(退出应用,不关闭连接):
- 打开应用程序,打开连接 1,打开连接 2(在同一个数据库上)
- 开始交易(连接 1)
INSERT
(连接 1)
- 结束事务(连接 1)
- 重复步骤 1-3 5 次
SELECT
(连接 1)// 所有数据都存在。
SELECT
(连接 2)// 所有数据都存在。
- 关闭应用程序(不关闭连接 1)
- 打开应用程序,打开连接3
SELECT
(连接 3)//部分数据丢失 - select 可能 return 5 个插入事务中有 2 个,因为例子。或 5 笔交易中的 0 笔。完全随机。
案例B(退出应用前关闭连接):
- 打开应用程序,打开连接 1,打开连接 2(在同一个数据库上)
- 开始交易(连接 1)
INSERT
(连接 1)
- 结束事务(连接 1)
- 重复步骤 1-3 5 次
SELECT
(连接 1)// 所有数据都存在。
SELECT
(连接 2)// 所有数据都存在。
- 关闭连接 1
- 关闭应用程序
- 打开应用程序,打开连接3
SELECT
(连接 3)//所有数据都存在。
案例C(关闭应用前执行检查点+不关闭连接):
- 打开应用程序,打开连接 1,打开连接 2(在同一个数据库上)
- 开始交易(连接 1)
INSERT
(连接 1)
- 结束事务(连接 1)
- 重复步骤 1-3 5 次
SELECT
(连接 1)// 所有数据都存在。
SELECT
(连接 2)// 所有数据都存在。
- 执行WAL检查点
- 关闭应用程序(不关闭连接 1)
- 打开应用程序,打开连接3
SELECT
(连接 3)//所有数据都存在。
总而言之,当我 SELECT
从 table 关闭应用程序之前,所有数据都存在,但在错误关闭应用程序后(例如,如果应用程序崩溃)一些数据我插入的不见了。但是,如果我在 之前 关闭应用程序(或在关闭应用程序之前关闭连接)执行检查点 - 所有数据都可用。
额外信息:
- 如果我在 重新打开应用程序(案例 A)后执行检查点 ,事务不会出现(不要从日志继续到主数据库文件)。
- WAL 挂钩:我已经使用
sqlite3_wal_hook
注册回调来检查事务是否实际提交到 WAL 日志文件,它显示页面已成功写入日志文件。
- WAL 文件:我尝试在 Android Studio 中使用设备文件资源管理器查看
-wal
文件,或者将其从内部存储复制到外部存储(/data/data/com.package .my/files),大多数时候它要么是空的,要么不存在。
- 线程安全:我尝试在打开数据库时使用 SQLITE_OPEN_FULLMUTEX 标志打开 SERIALIZED 线程安全模式:
sqlite3_open_v2(db_name.c_str(), &handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr);
没有任何区别。但是,它会导致从第二个连接读取时出现问题,所以我使用 sqlite3_open
而没有 SQLITE_OPEN_FULLMUTEX
。
堆栈:android 7 - JNI - c++ 11 - sqlite 3.27.2
更新。按照@bwt 的建议尝试了 PRAGMA synchronous = EXTRA
和 FULL
- 没有帮助。
代码:
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(), &handle); // rc = 0
// 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, &result, &errMsg); // rcWAL = 0
}
// close connection
int32 close() {
return sqlite3_close(handle);
}
// WAL checkpoint
sqlite3_exec(handle, "pragma wal_checkpoint;", processResults, &result, &errMsg);
// Insert
EventPtr persist(EventPtr obj) {
vector<DBData*> args;
int beginResult = sqlite3_exec(_connector->handle, "BEGIN TRANSACTION;", NULL, NULL, NULL);
try {
args.push_back(&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(),&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 string& query, vector<DBData*> *args){
sqlite3_stmt *stmt;
cout << query ;
sqlite3_prepare_v2(handle, query.c_str(), query.size(), &stmt, NULL);
for(uint32 i = 0;args && 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), &stmt, NULL);
for(uint32 i = 0;args && 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;
}
// LUA parts: --------------------------------------------------------------
// 1.
local query = [[ SELECT val from Parameters WHERE name = "column_name"]]
local period = 0
for row in db:nrows(query) do
if row["val"] ~= nil then
period = row["val"]
end
end
// 2.
local table = {}
if json ~= nil then
table["event_id"] = in_json["event_id"]
local query = [[ SELECT * FROM Events WHERE event_id = "%s" ]]
query = string.format(query, table["event_id"])
for row in db:nrows(query) do
table = row
end
else
json = {}
local query = [[ SELECT * FROM Events order by created DESC LIMIT 1; ]]
for row in db:nrows(query) do
table = row
end
end
// 3.
function getRow(con, sql)
local iRow = nil
for a in con:nrows(sql) do
iRow = a
end
return iRow
end
local termRow = getRow(db,[[SELECT value FROM parameters WHERE name='column_name']])
// 4.
local stmt = db:prepare("SELECT value FROM parameters WHERE name='column_name'")
local cnt = 0
for row in stmt:nrows() do
cnt = cnt + 1
end
stmt:finalize()
// 5.
local param = "N"
for Parameter in db_transport:nrows([[SELECT val FROM Parameters WHERE name = 'param']]) do
param = SParameter["val"]
end
这一切都归结为我使用了两个不同的 SQLite 实例。
- C++ 使用的静态 libsqlite3 (3.27.2)
- 内部使用 sqlite 3.24.0
的 Lua 使用的静态 lsqlite3 (lsqlite3complete)
这里列出了为什么这是个坏主意的更详细的解释:https://www.sqlite.org/howtocorrupt.html#multiple_copies_of_sqlite_linked_into_the_same_application
解决方法:
在 C++ 端使用动态 libsqlite3.so 和链接到动态 libsqlite3.so 和 liblua.so 的动态 lsqlite3.so。这使得在 C++ 和 Lua 端使用相同的 sqlite3 库实例成为可能。
在与 LuaSQLite3 开发人员讨论后,lua.sqlite.org 上关于 lsqlite3complete 的文档已更新。
http://lua.sqlite.org/index.cgi/doc/tip/doc/lsqlite3.wiki#overview
问题:如果先前的连接未正确关闭,则无法使用 WAL 模式(预写日志)从新连接到 SQLite 数据库 SELECT
获取数据。主要问题是:为什么数据会丢失,有什么方法可以让丢失的交易消失吗?
我正在尝试将数据存储在 SQLite table 中,并启用了 WAL mode
。我将描述 3 种情况:情况 A 导致交易损失,情况 B 和 C - 不会。
情况A(退出应用,不关闭连接):
- 打开应用程序,打开连接 1,打开连接 2(在同一个数据库上)
- 开始交易(连接 1)
INSERT
(连接 1)- 结束事务(连接 1)
- 重复步骤 1-3 5 次
SELECT
(连接 1)// 所有数据都存在。SELECT
(连接 2)// 所有数据都存在。- 关闭应用程序(不关闭连接 1)
- 打开应用程序,打开连接3
SELECT
(连接 3)//部分数据丢失 - select 可能 return 5 个插入事务中有 2 个,因为例子。或 5 笔交易中的 0 笔。完全随机。
案例B(退出应用前关闭连接):
- 打开应用程序,打开连接 1,打开连接 2(在同一个数据库上)
- 开始交易(连接 1)
INSERT
(连接 1)- 结束事务(连接 1)
- 重复步骤 1-3 5 次
SELECT
(连接 1)// 所有数据都存在。SELECT
(连接 2)// 所有数据都存在。- 关闭连接 1
- 关闭应用程序
- 打开应用程序,打开连接3
SELECT
(连接 3)//所有数据都存在。
案例C(关闭应用前执行检查点+不关闭连接):
- 打开应用程序,打开连接 1,打开连接 2(在同一个数据库上)
- 开始交易(连接 1)
INSERT
(连接 1)- 结束事务(连接 1)
- 重复步骤 1-3 5 次
SELECT
(连接 1)// 所有数据都存在。SELECT
(连接 2)// 所有数据都存在。- 执行WAL检查点
- 关闭应用程序(不关闭连接 1)
- 打开应用程序,打开连接3
SELECT
(连接 3)//所有数据都存在。
总而言之,当我 SELECT
从 table 关闭应用程序之前,所有数据都存在,但在错误关闭应用程序后(例如,如果应用程序崩溃)一些数据我插入的不见了。但是,如果我在 之前 关闭应用程序(或在关闭应用程序之前关闭连接)执行检查点 - 所有数据都可用。
额外信息:
- 如果我在 重新打开应用程序(案例 A)后执行检查点 ,事务不会出现(不要从日志继续到主数据库文件)。
- WAL 挂钩:我已经使用
sqlite3_wal_hook
注册回调来检查事务是否实际提交到 WAL 日志文件,它显示页面已成功写入日志文件。 - WAL 文件:我尝试在 Android Studio 中使用设备文件资源管理器查看
-wal
文件,或者将其从内部存储复制到外部存储(/data/data/com.package .my/files),大多数时候它要么是空的,要么不存在。 - 线程安全:我尝试在打开数据库时使用 SQLITE_OPEN_FULLMUTEX 标志打开 SERIALIZED 线程安全模式:
sqlite3_open_v2(db_name.c_str(), &handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr);
没有任何区别。但是,它会导致从第二个连接读取时出现问题,所以我使用 sqlite3_open
而没有 SQLITE_OPEN_FULLMUTEX
。
堆栈:android 7 - JNI - c++ 11 - sqlite 3.27.2
更新。按照@bwt 的建议尝试了 PRAGMA synchronous = EXTRA
和 FULL
- 没有帮助。
代码:
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(), &handle); // rc = 0
// 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, &result, &errMsg); // rcWAL = 0
}
// close connection
int32 close() {
return sqlite3_close(handle);
}
// WAL checkpoint
sqlite3_exec(handle, "pragma wal_checkpoint;", processResults, &result, &errMsg);
// Insert
EventPtr persist(EventPtr obj) {
vector<DBData*> args;
int beginResult = sqlite3_exec(_connector->handle, "BEGIN TRANSACTION;", NULL, NULL, NULL);
try {
args.push_back(&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(),&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 string& query, vector<DBData*> *args){
sqlite3_stmt *stmt;
cout << query ;
sqlite3_prepare_v2(handle, query.c_str(), query.size(), &stmt, NULL);
for(uint32 i = 0;args && 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), &stmt, NULL);
for(uint32 i = 0;args && 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;
}
// LUA parts: --------------------------------------------------------------
// 1.
local query = [[ SELECT val from Parameters WHERE name = "column_name"]]
local period = 0
for row in db:nrows(query) do
if row["val"] ~= nil then
period = row["val"]
end
end
// 2.
local table = {}
if json ~= nil then
table["event_id"] = in_json["event_id"]
local query = [[ SELECT * FROM Events WHERE event_id = "%s" ]]
query = string.format(query, table["event_id"])
for row in db:nrows(query) do
table = row
end
else
json = {}
local query = [[ SELECT * FROM Events order by created DESC LIMIT 1; ]]
for row in db:nrows(query) do
table = row
end
end
// 3.
function getRow(con, sql)
local iRow = nil
for a in con:nrows(sql) do
iRow = a
end
return iRow
end
local termRow = getRow(db,[[SELECT value FROM parameters WHERE name='column_name']])
// 4.
local stmt = db:prepare("SELECT value FROM parameters WHERE name='column_name'")
local cnt = 0
for row in stmt:nrows() do
cnt = cnt + 1
end
stmt:finalize()
// 5.
local param = "N"
for Parameter in db_transport:nrows([[SELECT val FROM Parameters WHERE name = 'param']]) do
param = SParameter["val"]
end
这一切都归结为我使用了两个不同的 SQLite 实例。
- C++ 使用的静态 libsqlite3 (3.27.2)
- 内部使用 sqlite 3.24.0 的 Lua 使用的静态 lsqlite3 (lsqlite3complete)
这里列出了为什么这是个坏主意的更详细的解释:https://www.sqlite.org/howtocorrupt.html#multiple_copies_of_sqlite_linked_into_the_same_application
解决方法: 在 C++ 端使用动态 libsqlite3.so 和链接到动态 libsqlite3.so 和 liblua.so 的动态 lsqlite3.so。这使得在 C++ 和 Lua 端使用相同的 sqlite3 库实例成为可能。
在与 LuaSQLite3 开发人员讨论后,lua.sqlite.org 上关于 lsqlite3complete 的文档已更新。
http://lua.sqlite.org/index.cgi/doc/tip/doc/lsqlite3.wiki#overview