WAL模式下SQLite插入5万条记录生成1.5GB WAL文件问题排查
SQLite WAL文件异常膨胀问题排查(插入5万条数据至1.5GB)
核心结论:插入前的DELETE操作不是WAL膨胀的主因
你执行的DELETE FROM fmap仅删除2-3条记录,WAL中只会记录这几条数据的删除日志,数据量极小,完全无法解释1.5GB的WAL膨胀,因此可以排除该操作的影响。
可能的根因分析
1. 单条插入独立提交(最可能)
你的代码中未显式开启事务,SQLite默认处于自动提交模式,每执行一次INSERT OR REPLACE就会创建并提交一个独立事务。WAL对每个独立事务都会写入额外的元数据(事务头、页标记等),5万条独立事务的累积开销会远大于实际数据本身,直接导致WAL文件巨量膨胀。
对比其他表插入正常的情况,大概率是其他表的插入逻辑使用了批量事务(BEGIN后批量插入再COMMIT),避免了单事务的额外开销。
2. max_id值远大于预期
代码中循环次数由max_id + 1决定,如果ixdb_largest_id返回的max_id不是约5万,而是远大于这个数值(比如200万),实际插入的记录数会远超预期,直接导致WAL数据量飙升。建议插入前打印max_id的实际值验证。
3. INSERT OR REPLACE触发不必要的替换逻辑
如果fmap表存在除id外的其他唯一索引,且__fmap_gen生成的数据存在重复的唯一键值,INSERT OR REPLACE会先删除旧记录再插入新记录,每条数据对应两次WAL写入操作,加倍日志量。可检查表结构和生成数据的唯一性确认。
代码修复方案
将所有插入操作包裹在一个事务中,消除单事务的额外开销:
//Insert into DB stmt_id = XDB_FMAP_INSERT; //INSERT OR REPLACE INTO fmap(id,pid,xt,size,xtime,ytime,selected) values (:id,:pid,:xt,:size,:xtime,:ytime,:selected); stmt = get_prepared_stmt(xdb, stmt_id); // 开启事务 status = sqlite3_exec(xdb->dbh->db, "BEGIN TRANSACTION;", NULL, NULL, NULL); if (status != 0) { goto err; } for (uint64_t i = 0; i < max_id + 1; ++i) { sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); sqlite3_bind_int64(stmt, 1, (uint64_t)(i + 1)); sqlite3_bind_int64(stmt, 2, fmap->hash[i].pid); sqlite3_bind_int64(stmt, 3, fmap->hash[i].xtid); sqlite3_bind_int64(stmt, 4, fmap->hash[i].size); sqlite3_bind_int64(stmt, 5, fmap->hash[i].xtime); sqlite3_bind_int64(stmt, 6, fmap->hash[i].ytime); sqlite3_bind_int64(stmt, 7, fmap->hash[i].selected); retry: status = sqlite3_step(stmt); if (status == SQLITE_BUSY || status == SQLITE_IOERR_BLOCKED) { printf("sqlite_step returned: %d . Hence retrying ", status); ++retry_count; if (retry_count <= ATTEMPTS) { //#define ATTEMPTS 5 sleep(SLEEP); //#define SLEEP 1 goto retry; } printf("Maximum number of retries(%d) exhausted|stmt=%s|err=%d", ATTEMPTS, sqlite3_expanded_sql(stmt), status); // 失败时回滚事务 sqlite3_exec(xdb->dbh->db, "ROLLBACK;", NULL, NULL, NULL); goto err; } else if (status != SQLITE_DONE) { printf("\nsqlite3_step() failed with error[%d].\n", status); // 失败时回滚事务 sqlite3_exec(xdb->dbh->db, "ROLLBACK;", NULL, NULL, NULL); status = ERR_SQLITE; goto err; } status = 0; } // 提交事务 status = sqlite3_exec(xdb->dbh->db, "COMMIT;", NULL, NULL, NULL); if (status != 0) { goto err; } if (stmt) { sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); }
额外验证步骤
- 打印
max_id的值,确认实际插入记录数是否为预期的5万条。 - 对比其他表的插入代码,确认是否使用了批量事务逻辑。
- 执行修改后的代码,监控WAL文件大小,正常情况下应该降到几十MB以内。
内容的提问来源于stack exchange,提问作者rg665n
相关产品推荐
相关产品推荐

