C++ 中使用 SQLite 数据库深度解析

发布于:2025-06-25 ⋅ 阅读:(16) ⋅ 点赞:(0)

SQLite 是一款轻量级、嵌入式的关系型数据库,因其零配置、文件型存储和出色的性能而被广泛应用于各种 C++ 项目中。本文将深入探讨如何在 C++ 开发中高效地使用 SQLite,涵盖基础操作、高级应用、性能优化及常见问题解决方案。

1. SQLite 简介与环境搭建

SQLite 是一个开源的嵌入式数据库引擎,它不依赖于独立的服务器进程,而是将数据库直接存储在文件中。这种特性使得 SQLite 非常适合嵌入式系统、移动应用和小型项目。

在 C++ 中使用 SQLite,首先需要下载 SQLite 的 C/C++ 接口库。你可以从 SQLite 官网 下载最新的 amalgamation 版本,其中包含 sqlite3.hsqlite3.c 两个核心文件。

将这两个文件添加到你的 C++ 项目中,然后在代码中包含头文件:

#include <sqlite3.h>

编译时需要链接 SQLite 库。如果使用 GCC 或 Clang,可以添加 -lsqlite3 选项:

g++ your_file.cpp -o your_program -lsqlite3
2. SQLite 基础操作
2.1 打开与关闭数据库

在 C++ 中操作 SQLite 数据库的第一步是打开数据库文件:

sqlite3* db;
int rc = sqlite3_open("example.db", &db);

if (rc) {
    std::cerr << "无法打开数据库: " << sqlite3_errmsg(db) << std::endl;
    return(0);
} else {
    std::cout << "数据库打开成功" << std::endl;
}

// 使用数据库...

// 操作完成后关闭数据库
sqlite3_close(db);
2.2 执行 SQL 语句

SQLite 的核心 API 是 sqlite3_exec,它可以执行任意 SQL 语句:

static int callback(void *NotUsed, int argc, char **argv, char **azColName) {
    for(int i = 0; i < argc; i++) {
        std::cout << azColName[i] << " = " << (argv[i] ? argv[i] : "NULL") << "\t";
    }
    std::cout << std::endl;
    return 0;
}

// 创建表
std::string sql = "CREATE TABLE IF NOT EXISTS TEST ("
                  "ID INT PRIMARY KEY     NOT NULL,"
                  "NAME           TEXT    NOT NULL,"
                  "AGE            INT     NOT NULL,"
                  "ADDRESS        CHAR(50),"
                  "SALARY         REAL );";

rc = sqlite3_exec(db, sql.c_str(), callback, 0, &zErrMsg);

if(rc != SQLITE_OK) {
    std::cerr << "SQL 错误: " << zErrMsg << std::endl;
    sqlite3_free(zErrMsg);
} else {
    std::cout << "表创建成功" << std::endl;
}
2.3 插入数据
sql = "INSERT INTO TEST (ID,NAME,AGE,ADDRESS,SALARY) "
      "VALUES (1, '张三', 32, '北京市', 20000.00 ); "
      "INSERT INTO TEST (ID,NAME,AGE,ADDRESS,SALARY) "
      "VALUES (2, '李四', 25, '上海市', 15000.00 );";

rc = sqlite3_exec(db, sql.c_str(), callback, 0, &zErrMsg);
2.4 查询数据
sql = "SELECT * FROM TEST";

rc = sqlite3_exec(db, sql.c_str(), callback, 0, &zErrMsg);
3. 预处理语句(Prepared Statements)

虽然 sqlite3_exec 很方便,但在处理大量重复查询时,使用预处理语句(Prepared Statements)会更高效:

sqlite3_stmt* stmt;
const char* sql = "SELECT * FROM TEST WHERE AGE > ?";

// 准备语句
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
    std::cerr << "准备语句失败: " << sqlite3_errmsg(db) << std::endl;
    return;
}

// 绑定参数
if (sqlite3_bind_int(stmt, 1, 25) != SQLITE_OK) {
    std::cerr << "绑定参数失败: " << sqlite3_errmsg(db) << std::endl;
    sqlite3_finalize(stmt);
    return;
}

// 执行查询
while (sqlite3_step(stmt) == SQLITE_ROW) {
    int id = sqlite3_column_int(stmt, 0);
    const unsigned char* name = sqlite3_column_text(stmt, 1);
    int age = sqlite3_column_int(stmt, 2);
    const unsigned char* address = sqlite3_column_text(stmt, 3);
    double salary = sqlite3_column_double(stmt, 4);

    std::cout << "ID: " << id 
              << ", 姓名: " << name 
              << ", 年龄: " << age 
              << ", 地址: " << address 
              << ", 工资: " << salary << std::endl;
}

// 释放资源
sqlite3_finalize(stmt);

预处理语句的优势:

  • 避免 SQL 注入攻击
  • 提高重复查询的执行效率
  • 更好地处理二进制数据
4. 事务处理

SQLite 支持事务,可以显著提高批量操作的性能:

// 开始事务
sqlite3_exec(db, "BEGIN TRANSACTION", nullptr, 0, nullptr);

try {
    // 执行多个插入操作
    for (int i = 0; i < 1000; ++i) {
        std::string sql = "INSERT INTO TEST (ID, NAME, AGE) VALUES (" 
                         + std::to_string(i) + ", '用户" + std::to_string(i) + "', " 
                         + std::to_string(20 + i % 30) + ")";
                         
        if (sqlite3_exec(db, sql.c_str(), nullptr, 0, nullptr) != SQLITE_OK) {
            throw std::runtime_error(sqlite3_errmsg(db));
        }
    }
    
    // 提交事务
    sqlite3_exec(db, "COMMIT", nullptr, 0, nullptr);
    std::cout << "事务提交成功" << std::endl;
} catch (const std::exception& e) {
    // 回滚事务
    sqlite3_exec(db, "ROLLBACK", nullptr, 0, nullptr);
    std::cerr << "事务回滚: " << e.what() << std::endl;
}
5. 错误处理与资源管理

在使用 SQLite 的 C API 时,正确的错误处理和资源管理至关重要:

// 封装 SQLite 操作的类示例
class SQLiteDB {
private:
    sqlite3* db;
    bool isOpen;
    
public:
    SQLiteDB(const std::string& dbName) : isOpen(false) {
        if (sqlite3_open(dbName.c_str(), &db) != SQLITE_OK) {
            std::cerr << "无法打开数据库: " << sqlite3_errmsg(db) << std::endl;
        } else {
            isOpen = true;
        }
    }
    
    ~SQLiteDB() {
        if (isOpen) {
            sqlite3_close(db);
        }
    }
    
    bool execute(const std::string& sql) {
        char* zErrMsg = 0;
        int rc = sqlite3_exec(db, sql.c_str(), nullptr, 0, &zErrMsg);
        
        if (rc != SQLITE_OK) {
            std::cerr << "SQL 错误: " << zErrMsg << std::endl;
            sqlite3_free(zErrMsg);
            return false;
        }
        
        return true;
    }
    
    // 其他方法...
};
6. 性能优化

在处理大量数据时,SQLite 的性能优化非常重要:

  1. 使用事务:将多个操作放在一个事务中可以显著提高性能
  2. 预处理语句:减少 SQL 解析时间
  3. 索引优化:为经常查询的列创建索引
  4. 调整 SQLite 缓存大小
    sqlite3_exec(db, "PRAGMA cache_size = 10000", nullptr, 0, nullptr);
    
  5. 禁用同步
    sqlite3_exec(db, "PRAGMA synchronous = OFF", nullptr, 0, nullptr);
    

    注意:这会提高写入性能,但在系统崩溃时可能导致数据丢失
7. 高级应用:自定义函数

SQLite 允许注册自定义 C++ 函数,扩展其功能:

// 自定义平方函数
static void sql_square(sqlite3_context* context, int argc, sqlite3_value** argv) {
    if (argc != 1) {
        sqlite3_result_error(context, "square() 需要一个参数", -1);
        return;
    }
    
    if (sqlite3_value_type(argv[0]) != SQLITE_INTEGER) {
        sqlite3_result_error(context, "square() 参数必须是整数", -1);
        return;
    }
    
    long long value = sqlite3_value_int64(argv[0]);
    sqlite3_result_int64(context, value * value);
}

// 注册自定义函数
sqlite3_create_function(db, "square", 1, SQLITE_UTF8, 0, sql_square, 0, 0);

// 使用自定义函数
std::string sql = "SELECT ID, NAME, square(AGE) FROM TEST";
8. 线程安全

SQLite 在多线程环境中的行为可以通过编译选项和运行时参数控制:

// 设置多线程模式
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);

// 或者设置序列化模式(线程安全但性能较低)
sqlite3_config(SQLITE_CONFIG_SERIALIZED);

注意:

  • 单连接不支持多线程并发使用
  • 多连接模式下,每个线程应该有自己的数据库连接
9. 总结与最佳实践
  • 使用预处理语句处理动态 SQL,防止注入攻击
  • 对批量操作使用事务提高性能
  • 合理使用索引优化查询
  • 实现完善的错误处理机制
  • 遵循 RAII 原则管理数据库连接
  • 在性能敏感的场景下调整 SQLite 配置参数

网站公告

今日签到

点亮在社区的每一天
去签到