SQLite3 :字典入库与数据查询
一、SQLite3 基础认知
1. 数据库分类(按规模划分)
关系型数据库是当前结构化数据存储的主流,按部署规模与适用场景可分为三类:
| 规模 | 代表产品 | 适用场景 |
|---|---|---|
| 大型 | ORACLE | 企业级核心业务、海量数据存储与高并发(如金融、电商) |
| 中型 | MySQL、MSSQL | 中小型网站、后台管理系统(如企业OA、电商后台) |
| 小型/嵌入式 | SQLite3、DB2、PowDB | 嵌入式设备、本地小型应用(如单片机、手机本地缓存、桌面工具) |
2. 核心名词解释
- DB(Database) :数据库,存储数据的容器(如
123.db),本质是一个或一组文件,用于组织、存储结构化数据。 - DBMS(Database Management System):数据库管理系统,操作和管理数据库的软件(如 SQLite3、MySQL),提供数据的增删改查、备份、恢复等功能。
- MIS(Management Information System):管理信息系统,基于数据库构建的业务管理平台(如学生成绩管理系统、库存管理系统)。
- OA(Office Automation):办公自动化系统,依托数据库实现日常办公流程数字化(如考勤、审批、文件管理)。
3. SQLite3 核心特点(嵌入式首选)
- 开源免费:遵循 GNU 协议,C 语言开发,源码可追溯、可定制。
- 轻量级小巧:核心代码仅 1 万行左右,总体积小于 10M,占用系统资源极少。
- 绿色免安装:无需部署服务端,无需配置,直接操作本地数据库文件,开箱即用。
- 文件型数据库:整个数据库对应一个本地
.db文件,可直接拷贝、移动,跨平台兼容性强。 - 大容量支持:单数据库文件最大容量可达 2T,满足绝大多数小型应用与嵌入式场景的存储需求。
- 兼容标准 SQL:支持绝大多数关系型数据库的标准 SQL 语句,学习成本低,迁移便捷。
4. SQLite3 安装与编译(Ubuntu 环境)
(1)在线安装
bash
# 安装 SQLite3 客户端(命令行操作)
sudo apt-get install sqlite3
# 安装 SQLite3 开发依赖库(C 语言编程必备)
sudo apt-get install libsqlite3-dev
(2)验证安装
bash
# 查看 SQLite3 版本
sqlite3 --version
# 查看帮助文档
sqlite3 --help
(3)C 语言程序编译
SQLite3 编程需链接专用库,编译命令格式如下:
bash
gcc 你的程序.c -o 可执行文件 -lsqlite3
# 若程序涉及线程操作,需额外链接 pthread 库
gcc 你的程序.c -o 可执行文件 -lsqlite3 -lpthread
二、SQLite3 交互式命令行实操
1. 核心终端指令(以 . 开头)
SQLite3 命令行操作分为「系统维护指令」(以 . 开头)和「标准 SQL 语句」(以 ; 结尾),核心系统维护指令如下:
| 指令 | 功能说明 |
|---|---|
.database |
查看当前数据库关联的本地文件路径 |
.tables |
列出当前数据库中的所有数据表 |
.schema [表名] |
显示指定表的建表语句(无表名则显示所有表结构) |
.q/.quit/.exit |
退出 SQLite3 交互式环境 |
.header on |
开启表头显示(查询结果显示字段名) |
.dump [表名] |
导出指定表(或整个数据库)的结构与数据到终端 |
(1)数据库的打开与创建
SQLite3 中,打开一个不存在的数据库文件会自动创建该数据库:
bash
# 格式:sqlite3 数据库文件名.db
sqlite3 123.db
执行后进入交互式提示符 sqlite>,表示数据库连接成功,此时当前目录会生成 123.db 文件(若不存在)。
(2)数据导出与导入(备份与恢复)
bash
# 导出数据库:将 123.db 完整导出到 dict_backup.sql 脚本文件
sqlite3 123.db .dump > dict_backup.sql
# 导入数据库:将 dict_backup.sql 中的表结构与数据导入到 new_123.db
sqlite3 new_123.db < dict_backup.sql
2. 标准 SQL 语句实战(增删改查)
所有 SQL 语句必须以 ; 结尾,否则会进入续行提示符 ...>,补充 ; 即可执行,核心操作如下:
(1)创建表(DDL 语句)
sql
-- 基础建表:定义字段与类型
create table user(id int, name char, age int);
-- 进阶建表:自增主键 + 非空约束(推荐)
create table if not exists dict(
id integer primary key autoincrement, -- 自增主键,无需手动指定
word text not null, -- 单词字段,非空约束
explain text not null -- 释义字段,非空约束
);
SQLite3 支持核心数据类型:int(整数)、text(字符串)、real(浮点型)、blob(二进制数据)。
(2)删除表(DDL 语句)
sql
-- 格式:drop table 表名;
drop table user;
-- 安全删除:先判断表是否存在(避免报错)
drop table if exists user;
(3)插入数据(DML 语句)
sql
-- 方式 1:指定字段插入
insert into user (id, name, age) values (1, '张三', 20);
-- 方式 2:不指定字段,按建表顺序插入所有字段
insert into user values (2, '李四', 22);
-- 方式 3:自增主键表插入(省略 id 字段)
insert into dict (word, explain) values ('apple', 'n. 苹果');
(4)查询数据(DQL 语句)
sql
-- 方式 1:查询全表所有数据
select * from user;
-- 方式 2:查询指定字段
select name, age from user;
-- 方式 3:带条件查询(where 子句)
select * from user where age > 20 and age < 30;
-- 方式 4:模糊查询(通配符)
select * from user where name like 'zhang_'; -- _ 匹配单个任意字符
select * from user where name like 'zhang%'; -- % 匹配任意多个任意字符
-- 方式 5:排序查询(order by)
select * from user where age > 18 order by age desc; -- desc 降序,asc 升序(默认)
(5)修改数据(DML 语句)
sql
-- 格式:update 表名 set 字段1=值1, 字段2=值2 where 条件;
update user set age = 23 where name = '李四';
update dict set explain = 'n. 苹果;苹果树' where word = 'apple';
(6)删除数据(DML 语句)
sql
-- 方式 1:删除指定条件的数据(推荐,避免误删全表)
delete from user where id = 1;
delete from dict where word = 'apple';
-- 方式 2:删除全表数据(谨慎使用,不可恢复)
delete from user;
三、SQLite3 C 语言编程核心接口与回调函数
1. 三大核心函数
SQLite3 C 语言编程的核心框架为「打开数据库 → 执行 SQL 语句 → 关闭数据库」,对应三大核心函数:
(1)打开/创建数据库:sqlite3_open
c
int sqlite3_open(char *path, sqlite3 **db);
- 功能:打开指定路径的数据库文件,若文件不存在则自动创建。
- 参数:
path:数据库文件路径(如 "./123.db")。db:数据库句柄指针,后续所有数据库操作均依赖该句柄。
- 返回值:成功返回
SQLITE_OK(0),失败返回非 0 错误码。 - 错误处理:通过
sqlite3_errmsg(db)获取详细错误信息。
(2)执行 SQL 语句:sqlite3_exec
c
int sqlite3_exec(sqlite3 *db, char *sql, sqlite3_callback fun, void *arg, char **errmsg);
- 功能:执行任意标准 SQL 语句(建表、增、删、改、查均可)。
- 参数:
db:已打开的数据库句柄。sql:要执行的 SQL 语句(以;结尾)。fun:回调函数(仅执行查询语句时需要,用于接收查询结果;非查询语句传NULL)。arg:传递给回调函数的自定义参数,无参数则传NULL。errmsg:存储执行错误信息,执行成功为NULL,需手动释放内存。
- 返回值:成功返回
SQLITE_OK(0),失败返回非 0 错误码。
(3)关闭数据库:sqlite3_close
c
int sqlite3_close(sqlite3 *db);
- 功能:关闭已打开的数据库,释放数据库句柄与系统资源。
- 参数:
db:已打开的数据库句柄。 - 返回值:成功返回
SQLITE_OK(0),失败返回非 0 错误码。 - 注意:关闭前需释放
errmsg指向的内存(sqlite3_free(errmsg)),避免内存泄露。
2. 回调函数的使用(查询结果接收)
当使用 sqlite3_exec 执行查询语句(select)时,需要通过回调函数接收查询结果,核心特点与示例如下:
(1)回调函数核心规则
- 回调函数会被
sqlite3_exec多次调用,查询结果有多少条记录,回调函数就调用多少次。 - 回调函数的返回值必须为 0,否则会终止查询,仅返回当前已获取的记录。
- 回调函数的参数由
sqlite3_exec自动填充,无需手动赋值。
(2)回调函数示例(打印查询结果)
c
#include <sqlite3.h>
#include <stdio.h>
// 回调函数:接收并打印查询结果
int show(void* arg, int col, char** result, char** title)
{
// static 变量:确保表头仅打印一次
static int flag = 0;
int i = 0;
// 第一次调用:打印表头(字段名)
if (0 == flag)
{
flag = 1;
for (i = 0; i < col; i++)
{
printf("%s\t\t", title[i]); // title[i]:第 i 列的字段名
}
printf("\n");
}
// 打印当前记录的所有字段值
for (i = 0; i < col; i++)
{
printf("%s\t\t", result[i]); // result[i]:第 i 列的字段值
}
printf("\n");
// 必须返回 0,否则终止查询
return 0;
}
(3)查询语句执行示例
c
int main()
{
sqlite3* db = NULL;
int ret = sqlite3_open("123.db", &db);
if (ret != SQLITE_OK)
{
printf("open db failed:%s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
char* errMsg = NULL;
char sql_cmd[512] = "select * from user;";
// 执行查询语句,指定回调函数 show
ret = sqlite3_exec(db, sql_cmd, show, NULL, &errMsg);
if (ret != SQLITE_OK)
{
printf("exec sql failed:%s\n", errMsg);
sqlite3_free(errMsg); // 释放错误信息内存
sqlite3_close(db);
return -1;
}
sqlite3_close(db);
return 0;
}
四、实战项目 1:dict.txt 字典文件批量入库
1. 项目需求
将 /home/linux/dict.txt 中的字典数据(每行格式:单词 释义)批量插入到 123.db 的 dict 表中,解决特殊字符报错、数据分割不完整等问题。
2. 核心难点与解决方案
| 核心难点 | 解决方案 |
|---|---|
| SQL 特殊字符(如单引号)导致语法错误 | 实现 sql_escape 函数,将单引号 ' 转义为 '' |
| 单词与释义间多空格/制表符分隔 | 用 strtok 以 空格/制表符 为分隔符,拆分单词与释义 |
| 多次运行导致主键冲突 | 表中 id 设为自增主键(AUTOINCREMENT) |
| 内存泄露 | 执行失败时及时释放 errmsg,操作完成后关闭文件与数据库 |
3. 完整实战代码
c
#include <sqlite3.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_LINE_LEN 2048 // 适配字典单行最大长度(含长释义)
#define MAX_SQL_LEN 4096 // 适配拼接后的长 SQL 语句
#define DICT_PATH "/home/linux/dict.txt" // 字典文件路径
// SQL 特殊字符转义:将 ' 转为 '',避免 SQL 语法错误
void sql_escape(char *dest, const char *src, int dest_len) {
int i = 0, j = 0;
while (src[i] != '\0' && j < dest_len - 2) {
if (src[i] == '\'') {
dest[j++] = '\'';
dest[j++] = '\'';
} else {
dest[j++] = src[i];
}
i++;
}
dest[j] = '\0';
}
int main(int argc, char** argv) {
sqlite3* db = NULL;
char* errmsg = NULL;
int ret;
// 1. 打开/创建数据库 123.db
ret = sqlite3_open("123.db", &db);
if (ret != SQLITE_OK) {
fprintf(stderr, "错误:打开数据库失败 - %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
printf("成功:打开/创建数据库123.db\n");
// 2. 创建 dict 表(自增主键,避免重复插入冲突)
const char sql_create[] = "CREATE TABLE IF NOT EXISTS dict ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"word TEXT NOT NULL,"
"explain TEXT NOT NULL);";
ret = sqlite3_exec(db, sql_create, NULL, NULL, &errmsg);
if (ret != SQLITE_OK) {
fprintf(stderr, "错误:创建表失败 - %s\n", errmsg);
sqlite3_free(errmsg);
sqlite3_close(db);
return 1;
}
printf("成功:创建/验证dict表\n");
sqlite3_free(errmsg);
errmsg = NULL;
// 3. 打开 dict.txt 文件(只读模式)
FILE *fp = fopen(DICT_PATH, "r");
if (fp == NULL) {
perror("错误:打开dict.txt失败");
fprintf(stderr, "请确认路径:%s 是否正确\n", DICT_PATH);
sqlite3_close(db);
return 1;
}
printf("成功:打开dict.txt文件,开始读取数据\n");
// 4. 逐行读取字典文件,批量插入数据库
char line_buf[MAX_LINE_LEN];
char sql_insert[MAX_SQL_LEN];
char word[256], explain[MAX_LINE_LEN];
char escape_word[512], escape_explain[MAX_LINE_LEN * 2];
while (fgets(line_buf, MAX_LINE_LEN, fp) != NULL) {
// 去除行尾换行符、回车符,避免插入无效字符
line_buf[strcspn(line_buf, "\n\r")] = '\0';
if (strlen(line_buf) == 0) {
continue; // 跳过空行
}
// 拆分单词与释义(支持空格、制表符分隔)
char *token = strtok(line_buf, " \t");
if (token == NULL) {
fprintf(stderr, "警告:无效行(跳过)- %s\n", line_buf);
continue;
}
strncpy(word, token, sizeof(word) - 1);
word[sizeof(word) - 1] = '\0';
// 提取剩余部分作为释义,去除开头多余空格
token = strtok(NULL, "");
if (token == NULL) {
fprintf(stderr, "警告:无释义(跳过)- 单词:%s\n", word);
continue;
}
while (*token == ' ' || *token == '\t') token++;
strncpy(explain, token, sizeof(explain) - 1);
explain[sizeof(explain) - 1] = '\0';
// 特殊字符转义,避免 SQL 语法错误
sql_escape(escape_word, word, sizeof(escape_word));
sql_escape(escape_explain, explain, sizeof(escape_explain));
// 拼接 SQL 插入语句
snprintf(sql_insert, MAX_SQL_LEN,
"INSERT INTO dict (word, explain) VALUES ('%s', '%s');",
escape_word, escape_explain);
// 执行插入操作
ret = sqlite3_exec(db, sql_insert, NULL, NULL, &errmsg);
if (ret != SQLITE_OK) {
fprintf(stderr, "错误:插入失败 - 单词:%s | 错误信息:%s\n", word, errmsg);
sqlite3_free(errmsg);
errmsg = NULL;
continue;
}
printf("成功:插入 - 单词:%s | 释义:%s\n", word, explain);
}
// 5. 资源释放
fclose(fp);
sqlite3_free(errmsg);
sqlite3_close(db);
printf("\n全部操作完成:dict.txt数据已插入123.db的dict表\n");
return 0;
}
4. 执行步骤与验证
(1)编译代码
bash
gcc dict2db.c -o dict2db -lsqlite3 -lpthread
(2)运行程序
bash
./dict2db
(3)验证插入结果
bash
# 打开 123.db 数据库
sqlite3 123.db
# 查看 dict 表结构
.schema dict
# 查询前 10 条字典数据
select * from dict limit 10;
# 退出数据库
.q
五、实战项目 2:用户信息查询与登录验证
1. 项目需求
基于 123.db 的 user 表,实现用户信息查询与登录验证,通过回调函数标记用户是否存在。
2. 完整实战代码
c
#include <sqlite3.h>
#include <stdio.h>
#include <string.h>
// 回调函数:标记用户是否存在(通过 arg 传递 flag 变量)
int show(void* arg,int col,char** result,char** title)
{
// 将 arg 转为 int 指针,修改 flag 为 1(表示找到用户)
*(int*)arg = 1;
return 0;
}
int main()
{
char name[50] = {0};
char pass[50] = {0};
// 接收用户输入
printf("请输入用户名:");
fgets(name, sizeof(name), stdin);
// 去除输入中的换行符
name[strcspn(name, "\n")] = '\0';
printf("请输入密码:");
fgets(pass, sizeof(pass), stdin);
pass[strcspn(pass, "\n")] = '\0';
// 打开数据库
sqlite3* db = NULL;
int ret = sqlite3_open("123.db",&db);
if(ret != SQLITE_OK)
{
printf("open db failed:%s\n",sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
// 拼接查询 SQL 语句
char* errMsg = NULL;
int flag = 0; // 标记用户是否存在,0 不存在,1 存在
char sql_cmd[512] = {0};
sprintf(sql_cmd, "select * from user where name = '%s' and pass = '%s'", name, pass);
// 执行查询,通过回调函数修改 flag
ret = sqlite3_exec(db,sql_cmd,show,&flag,&errMsg);
if(ret != SQLITE_OK)
{
printf("exec sql failed:%s\n",errMsg);
sqlite3_free(errMsg);
sqlite3_close(db);
return -1;
}
// 输出验证结果
if(flag == 0)
{
printf("未找到用户:%s\n",name);
}
else
{
printf("用户验证通过:%s\n",name);
}
// 关闭数据库
sqlite3_close(db);
return 0;
}
3. 关键说明
- 利用
void* arg传递自定义变量flag,实现回调函数与主函数的数据交互。 - 输入处理时去除换行符,避免因
fgets读取的换行符导致查询失败。 - 若用户表中无
pass字段,需修改 SQL 语句与表结构,适配实际需求。
六、常见问题与避坑指南
- no such column: xxx :SQL 语句中字符串值未用单引号包裹,或特殊字符未转义,需用
'xxx'包裹字符串,并通过sql_escape处理单引号。 - double free detected in tcache 2 :
errmsg被重复释放,仅在sqlite3_exec执行失败时释放errmsg,且释放后重置为NULL,避免后续重复释放。 - no such table: xxx :未创建表直接插入数据,需先执行
CREATE TABLE语句,推荐使用IF NOT EXISTS避免重复建表报错。 - 主键冲突 :手动指定
id插入时重复,需将id设为自增主键(AUTOINCREMENT),插入时省略id字段。 - 无法打开 dict.txt :文件路径错误或无读取权限,需确认路径正确性,执行
chmod +r 文件名赋予读取权限。
七、总结与拓展
1. 核心知识点梳理
- SQLite3 是轻量级嵌入式关系型数据库,无需服务端,适合小型应用与嵌入式设备。
- C 语言编程核心框架:
sqlite3_open打开数据库 →sqlite3_exec执行 SQL →sqlite3_close关闭数据库。 - 回调函数是查询结果接收的关键,需遵循「多次调用、返回 0」的规则。
- 批量入库需解决数据分割、特殊字符转义、资源释放三大问题。
2. 拓展方向
- HTML 可视化:结合 HTML 与 CGI 技术,实现字典数据的网页查询与展示(参考 W3School HTML 手册)。
- 数据备份与恢复 :基于
.dump指令实现数据库的自动备份与批量恢复。 - 性能优化 :对于超大字典文件,采用事务(
BEGIN TRANSACTION/COMMIT)减少数据库 IO,提升插入效率。 - 功能扩展:实现字典的模糊查询、新增、修改、删除功能,打造完整的字典管理系统。