在嵌入式Linux开发中,数据持久化一直是个有趣的话题。想象一下,你正在为社区水果店开发一个智能库存管理系统,需要记录每种水果的进货、销售和实时库存。这个系统需要在树莓派这样的低功耗设备上稳定运行,而SQLite3正是解决这个问题的完美选择。它不需要单独的服务器进程,整个数据库就是一个文件,却提供了完整的SQL支持。
SQLite3在嵌入式系统中表现出色的几个关键特性:
提示:SQLite3特别适合需要频繁读写但并发不高的场景,比如我们的水果库存系统每天可能只有几十次操作。
与其他嵌入式数据库方案对比:
| 特性 | SQLite3 | Berkeley DB | MySQL Embedded |
|---|---|---|---|
| 内存占用 | ~300KB | ~500KB | ~5MB |
| SQL支持 | 完整 | 无 | 完整 |
| 事务支持 | ACID | 基本 | ACID |
| 部署复杂度 | 无 | 中等 | 高 |
水果库存系统的核心是设计合理的数据表结构。我们需要记录每次进货、销售的详细信息,同时能快速查询当前库存。
c复制typedef struct {
char time[20]; // 交易时间
char name[16]; // 水果名称
float cost; // 成本价
float price; // 售价
char in_out[5]; // 进货'in'/出货'out'
float number; // 交易数量
float money; // 交易金额
float total; // 库存总量
} fruit_t;
对应的SQL建表语句:
sql复制CREATE TABLE IF NOT EXISTS fru (
time CHAR(20), -- 格式: YYYY-MM-DD HH:MM:SS
name CHAR(16), -- 水果名称
cost REAL, -- 成本价
price REAL, -- 售价
in_out CHAR(5), -- 'in'/'out'/'update'
number REAL, -- 交易数量
money REAL, -- 交易金额
total REAL -- 库存总量
);
这种设计有几个巧妙之处:
好的代码结构应该将数据库操作封装成独立的函数模块。以下是几个关键功能的实现:
c复制sqlite3 *db_open(const char *filename) {
sqlite3 *db;
if(sqlite3_open(filename, &db) != SQLITE_OK) {
fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db));
return NULL;
}
// 启用外键约束
sqlite3_exec(db, "PRAGMA foreign_keys = ON;", 0, 0, 0);
return db;
}
void db_close(sqlite3 *db) {
if(db) sqlite3_close(db);
}
进货时需要处理两种情况:新水果品种和已有品种的补货。
c复制int fruit_in(sqlite3 *db, fruit_t *fruit) {
char sql[512];
fruit_t *last = get_last_record(db, fruit->name);
if(last) { // 已有记录
fruit->total = last->total + fruit->number;
} else { // 新品种
fruit->total = fruit->number;
}
snprintf(sql, sizeof(sql),
"INSERT INTO fru VALUES('%s','%s',%.2f,%.2f,'in',%.2f,%.2f,%.2f);",
fruit->time, fruit->name, fruit->cost, fruit->price,
fruit->number, fruit->cost*fruit->number, fruit->total);
return execute_sql(db, sql);
}
销售时需要检查库存是否充足,这是个典型的事务操作:
c复制int fruit_out(sqlite3 *db, fruit_t *fruit) {
char sql[512];
fruit_t *last = get_last_record(db, fruit->name);
if(!last || last->total < fruit->number) {
printf("库存不足!当前%s库存: %.2f\n",
fruit->name, last ? last->total : 0);
return -1;
}
fruit->total = last->total - fruit->number;
snprintf(sql, sizeof(sql),
"INSERT INTO fru VALUES('%s','%s',%.2f,%.2f,'out',%.2f,%.2f,%.2f);",
fruit->time, fruit->name, last->cost, last->price,
fruit->number, last->price*fruit->number, fruit->total);
return execute_sql(db, sql);
}
当需要处理大批量数据时,显式使用事务可以极大提高性能:
c复制void import_from_csv(sqlite3 *db, const char *csv_file) {
FILE *fp = fopen(csv_file, "r");
if(!fp) return;
char line[256];
sqlite3_exec(db, "BEGIN TRANSACTION;", 0, 0, 0);
while(fgets(line, sizeof(line), fp)) {
fruit_t fruit;
// 解析CSV行到fruit结构体
if(parse_csv_line(line, &fruit) == 0) {
fruit_in(db, &fruit);
}
}
sqlite3_exec(db, "COMMIT;", 0, 0, 0);
fclose(fp);
}
利用SQL的聚合函数可以轻松生成各种统计报表:
c复制void generate_report(sqlite3 *db, const char *start_date, const char *end_date) {
char sql[512];
snprintf(sql, sizeof(sql),
"SELECT name, SUM(CASE WHEN in_out='in' THEN number ELSE 0 END) as in_sum, "
"SUM(CASE WHEN in_out='out' THEN number ELSE 0 END) as out_sum, "
"MAX(total) as current_stock FROM fru "
"WHERE time BETWEEN '%s' AND '%s' GROUP BY name;",
start_date, end_date);
printf("\n=== 库存统计报表 %s 至 %s ===\n", start_date, end_date);
printf("%-15s %10s %10s %10s\n", "名称", "进货量", "销售量", "当前库存");
execute_sql_with_callback(db, sql, report_callback, NULL);
}
对于频繁访问的数据,可以使用内存数据库作为缓存:
c复制sqlite3 *create_memory_cache() {
sqlite3 *mem_db;
sqlite3_open(":memory:", &mem_db);
// 从磁盘数据库复制常用数据到内存
sqlite3_exec(mem_db,
"ATTACH DATABASE 'fru.db' AS disk;"
"CREATE TABLE fru AS SELECT * FROM disk.fru WHERE time > date('now','-7 day');"
"DETACH DATABASE disk;", 0, 0, 0);
return mem_db;
}
在x86主机上为ARM开发板交叉编译时需要注意:
bash复制arm-linux-gnueabihf-gcc -I/path/to/sqlite3/include \
-L/path/to/sqlite3/lib -o fruit_manager main.c -lsqlite3 -ldl -lpthread
关键点:
-I指定SQLite3头文件路径-L指定库文件路径-ldl和-lpthread是SQLite3需要的系统库在内存有限的嵌入式设备上,可以通过这些设置优化SQLite3:
c复制// 设置缓存大小为2MB
sqlite3_exec(db, "PRAGMA cache_size = -2000;", 0, 0, 0);
// 使用WAL模式获得更好的并发性能
sqlite3_exec(db, "PRAGMA journal_mode = WAL;", 0, 0, 0);
// 设置同步模式为NORMAL以平衡性能与安全性
sqlite3_exec(db, "PRAGMA synchronous = NORMAL;", 0, 0, 0);
一个完整的水果库存管理系统可以这样分层:
code复制+-----------------------+
| 用户界面层 | (命令行/Web/APP)
+-----------------------+
| 业务逻辑层 | (库存管理核心逻辑)
+-----------------------+
| 数据访问层 | (SQLite3操作封装)
+-----------------------+
| 持久化存储层 | (SQLite3数据库文件)
+-----------------------+
这种分层设计使得各模块职责清晰,便于维护和扩展。比如要增加网络功能,只需修改用户界面层,核心业务逻辑和数据访问层可以保持不变。