SQLite C API中的静态与非静态预处理语句

时间:2014-03-03 21:21:25

标签: c sqlite

我正在使用C和SQL中的终端应用程序来标记文件。文件/标记关系保存在SQLite 3数据库中。到目前为止,我一直在使用静态分配的预处理语句,这些语句在每次调用包装函数时都会被重置和绑定。最近我用valgrind测试了内存泄漏等,并且它抱怨很多,因为显然包装器中的静态sqlite3_stmt在程序终止时没有被释放。这也会导致数据库无法正常关闭(因为未完成的语句)。

这样做的最初原因是性能。我在the C interface intro中读到,重复使用准备好的语句非常有利于提高性能。

  

在现有的预准备语句上使用sqlite3_reset()而不是创建新的预准备语句可以避免对sqlite3_prepare()进行不必要的调用。在许多SQL语句中,运行sqlite3_prepare()所需的时间等于或超过sqlite3_step()所需的时间。因此,避免调用sqlite3_prepare()可以显着提高性能。

以下是一些显示我正在做的事情的代码:

int tag_file(const char *file, const char *tag)
{
    static sqlite3_stmt *sql_prep = NULL;
    static const char *sql_str = "INSERT OR IGNORE INTO Tag VALUES (?, ?);";

    // Prepare if null, else reset
    prepare_or_reset(&sql_prep, sql_str);

    if (sqlite3_bind_text(sql_prep, 1, file, -1, SQLITE_STATIC) != SQLITE_OK ||
        sqlite3_bind_text(sql_prep, 2, tag, -1, SQLITE_STATIC) != SQLITE_OK)
        return ERROR;

    if (sqlite3_step(sql_prep) != SQLITE_DONE)
        return ERROR;

    return SUCCESS;
}

我尝试过的,它将满足valgrind,但完全错过重用预准备语句的性能提升,使sql_prep非静态,并在结束时调用sqlite3_finalize(sql_prep)功能,但我想这是更糟糕的表现。我可以只用一次优雅的方式准备一次没有内存泄漏的声明吗?

我开始担心的另一件事是内存消耗。在未来,我计划为这个应用程序创建一个GUI,这将使所有这些在内存中保留更长时间。性能提升可能会更大,但该语句只是从第一次调用到退出时就位于堆上。这是公平的space-time tradoff吗?

编辑:将全部“静态”预处理语句保存在全局数组中会不会很奇怪/奇怪,我可以免费atexit

2 个答案:

答案 0 :(得分:1)

你提到了两个问题。

  1. 第一个基本上是如何管理准备好的陈述。

    这里似乎有必要重新设计一下int tag_file(const char *file, const char *tag)函数。

    要么将stmt维持在一个更高的级别,并且每次都将它传递给该函数。然后你可以在程序的最后清理。

    另一种方法是以一种方式定义你的函数的语义,你可以给它特殊的哨兵函数(可能只是(NULL, NULL)),这使得函数可以自由发表声明。

  2. 第二个问题是堆大小问题。只要你没有数百或数千个准备好的stmts,我认为你可以随时拥有它,因为它可能只花费几个字节。

答案 1 :(得分:1)

  1. 必须在关闭数据库之前完成所有语句。

    如果在tag_file等函数之外不知道语句,则无法执行此操作。 你应该有一个全局的语句列表,让prepare_or_reset函数动态地将语句添加到该列表中。

  2. 准备好的陈述很小;没有与GUI库分配的所有内容进行比较。很可能你甚至都没有注意到它们的内存使用情况。

    但是,如果您不经常执行准备语句,准备时间很明显,那么准备好的语句不会提高速度。所以这两种方式都无关紧要。 ☺