【问题标题】:SQLite3 - memory usage gradually increases until all available memory are consumedSQLite3 - 内存使用量逐渐增加,直到耗尽所有可用内存
【发布时间】:2014-09-05 01:11:08
【问题描述】:

我在长时间运行一个简单的单线程 C++ 程序时遇到内存使用问题(比如通宵运行)。该程序使用 SQLite3 API 打开一个数据库并在其中循环写入一些数据。我在两台不同的机器上运行该程序:一台桌面 Ubuntu Linux 和一台运行定制 Linux 的基于 ARM 的嵌入式设备。 在这两种情况下,我都得到了相同的结果:内存逐渐消耗,并且在应用程序运行时没有释放。我正在使用在后台运行的简单 bash 脚本检查内存使用情况:

while true;
do free -m;
sleep 2;
done

需要注意的是,我也在使用 SQLite 提供的 API 监控内存使用情况:

sqlite3_memory_used() 

API 报告的已用内存量相当稳定,但“free -m”报告不同并逐渐增加。

SQLite 源代码使用以下标志编译:

SQLITE_DEFAULT_TEMP_CACHE_SIZE=3
SQLITE_DEFAULT_WAL_AUTOCHECKPOINT=2
SQLITE_MAX_MMAP_SIZE=2048
SQLITE_ENABLE_MEMSYS5
SQLITE_ENABLE_MEMORY_MANAGEMENT
SQLITE_DEFAULT_CACHE_SIZE=3
SQLITE_DEFAULT_AUTOVACUUM=1
SQLITE_DEFAULT_PAGE_SIZE=512

请注意,在这个阶段我并不关心速度,但我主要关心的是内存使用,所以我设置参数的方式是让最少的数据缓存在内存中并尽快将它们推送到磁盘。

我还在每次迭代中使用“PRAGMA shrink_memory”。

为了尽量减少动态内存分配,我还为以下内存类型提供了静态数组:

SQLITE_CONFIG_HEAP
SQLITE_CONFIG_SCRATCH
SQLITE_CONFIG_PAGECACHE

写入数据库的代码 sn-p 如下所示:

char SQL_Statement[100]={0};
char *ErrMsg = 0;
for (int i = 0; i < 1000000; i++)
{
    sprintf(SQL_Statement, "INSERT INTO PointValue (TimeStamp, BlockId, PointId, Value) VALUES (%f, %d, %d, %d);",TimeStamp_ ,BlockId_, PointId_, Value_ );
    check =  sqlite3_exec(MyDB, SQL_Statement, callback, (void*)data, &ErrMsg); 
    sqlite3_free(ErrMsg);
}

【问题讨论】:

  • char SQL_Statement[100]={0}; 看看sprintf,如果你声明一个只有 100 个字符的数组,你就在刀刃上取得平衡。如果您通过声明这么小的数量来创建内存覆盖,我不会感到惊讶。
  • 应该是“少量”。
  • 感谢@PaulMcKenzie 的评论。我检查了生成的字符串的大小并确认它低于 100(它是 85 个字符)。所有参数都是固定值,不会增长。
  • 我不认为你可以用可调试的 sqlite 源代码在一夜之间让 Valgrind 松动?我想这会很有启发性。您是否体验过与 ARM 装备相似的关闭特性?
  • 有证据表明内存没有被SQLite使用。此外,该内存是由该进程消耗还是由文件缓存消耗?

标签: c++ linux memory sqlite


【解决方案1】:

sqlite3_exec的最后一个参数只有在初始化调用sqlite3_malloc的情况下发生错误时才设置,需要释放,但是当没有错误发生时设置为NULL,可能是有问题使用 NULL 变量调用 sqlite3_free。尝试添加条件:

if (ErrMsg != nullptr) // if C++11 or NULL o 0 if C++98
    sqlite3_free(ErrMsg);

查看网址:http://www.sqlite.org/c3ref/exec.html
正如 Sqlite3 文档所述,传递给回调的内存不需要任何免费调用,在 sqlite3_exec 内部调用 sqlite3_step() 女巫后,所有内存都将自动释放并且无效,因此,如果您需要将值保存在回调供以后使用,你需要复制它,指针会在下一次回调调用或之后执行的代码中失效。

【讨论】:

  • 感谢@NetVipeC 的回答。我已经在我的代码中应用了你的建议,但很遗憾没有帮助。
  • 其他建议是使用准备好的语句,SQL_Statement 在循环的每次传递中的初始化和可能的分配可能会耗尽,并且 100 个字符的限制在插入的数据可能代表将来出现问题。伪代码为:1-Prepare Statement one time. 2-Loop through data. 3-Bind Columns with data. 4-Step statement. 5-Reset Statement
  • @NetVipeC 如果sqlite3_exec的第三个参数的原型是int selectCB(void *data, int argc, char **argv, char **azColName);,那么argv azColName必须是free
【解决方案2】:

感谢大家的回答和 cmets。 已确认内存已被 linux 页面缓存消耗,这很好,因为 Linux 希望能够处理它并在另一个应用程序需要更多内存时释放不必要的页面。 只需使用一个简单的命令:

cat /proc/meminfo

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-07
    • 1970-01-01
    • 2017-12-27
    • 2017-04-08
    • 2014-01-03
    • 2015-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多