42、SQLite3 :字典入库与数据查询

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 核心特点(嵌入式首选)

  1. 开源免费:遵循 GNU 协议,C 语言开发,源码可追溯、可定制。
  2. 轻量级小巧:核心代码仅 1 万行左右,总体积小于 10M,占用系统资源极少。
  3. 绿色免安装:无需部署服务端,无需配置,直接操作本地数据库文件,开箱即用。
  4. 文件型数据库:整个数据库对应一个本地 .db 文件,可直接拷贝、移动,跨平台兼容性强。
  5. 大容量支持:单数据库文件最大容量可达 2T,满足绝大多数小型应用与嵌入式场景的存储需求。
  6. 兼容标准 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)回调函数核心规则
  1. 回调函数会被 sqlite3_exec 多次调用,查询结果有多少条记录,回调函数就调用多少次。
  2. 回调函数的返回值必须为 0,否则会终止查询,仅返回当前已获取的记录。
  3. 回调函数的参数由 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.dbdict 表中,解决特殊字符报错、数据分割不完整等问题。

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.dbuser 表,实现用户信息查询与登录验证,通过回调函数标记用户是否存在。

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. 关键说明

  1. 利用 void* arg 传递自定义变量 flag,实现回调函数与主函数的数据交互。
  2. 输入处理时去除换行符,避免因 fgets 读取的换行符导致查询失败。
  3. 若用户表中无 pass 字段,需修改 SQL 语句与表结构,适配实际需求。

六、常见问题与避坑指南

  1. no such column: xxx :SQL 语句中字符串值未用单引号包裹,或特殊字符未转义,需用 'xxx' 包裹字符串,并通过 sql_escape 处理单引号。
  2. double free detected in tcache 2errmsg 被重复释放,仅在 sqlite3_exec 执行失败时释放 errmsg,且释放后重置为 NULL,避免后续重复释放。
  3. no such table: xxx :未创建表直接插入数据,需先执行 CREATE TABLE 语句,推荐使用 IF NOT EXISTS 避免重复建表报错。
  4. 主键冲突 :手动指定 id 插入时重复,需将 id 设为自增主键(AUTOINCREMENT),插入时省略 id 字段。
  5. 无法打开 dict.txt :文件路径错误或无读取权限,需确认路径正确性,执行 chmod +r 文件名 赋予读取权限。

七、总结与拓展

1. 核心知识点梳理

  1. SQLite3 是轻量级嵌入式关系型数据库,无需服务端,适合小型应用与嵌入式设备。
  2. C 语言编程核心框架:sqlite3_open 打开数据库 → sqlite3_exec 执行 SQL → sqlite3_close 关闭数据库。
  3. 回调函数是查询结果接收的关键,需遵循「多次调用、返回 0」的规则。
  4. 批量入库需解决数据分割、特殊字符转义、资源释放三大问题。

2. 拓展方向

  1. HTML 可视化:结合 HTML 与 CGI 技术,实现字典数据的网页查询与展示(参考 W3School HTML 手册)。
  2. 数据备份与恢复 :基于 .dump 指令实现数据库的自动备份与批量恢复。
  3. 性能优化 :对于超大字典文件,采用事务(BEGIN TRANSACTION/COMMIT)减少数据库 IO,提升插入效率。
  4. 功能扩展:实现字典的模糊查询、新增、修改、删除功能,打造完整的字典管理系统。
相关推荐
沙蒿同学5 分钟前
我把架构约定编译成了会变红的测试:Wails v2 + Go + Vue3 桌面脚手架实战
前端·后端·github
cpolar技术支持7 分钟前
本地 Playwright 测试报告怎么远程复盘?Trace Viewer 跑起来后,用 cpolar 分享失败现场
前端·自动化测试·测试工具·cpolar·playwright
wordbaby8 分钟前
企业级后台管理系统路由设计与最佳实践指南
前端
pilifeng19 分钟前
Ubuntu Linux 目录结构详解
linux·ubuntu
电棍23315 分钟前
ubuntu网卡失效
linux
胡志辉的博客17 分钟前
【完全开源】IP 纯净度检测 可一键部署到自己的CF
前端·javascript·chrome·ip·chromium
律宏阔19 分钟前
VMware 能识别 USB 设备,但无法连接(灰色)
linux
YangYang9YangYan24 分钟前
2026 电商数据运营校招 JD 梳理|岗位任务、技能与面试考点汇总
大数据·数据库·数据分析
Sun 328525 分钟前
读懂集中式主备的集群状态与节点角色
数据库·opengauss·集群·节点角色·磐维数据库·运行状态
Rabitebla26 分钟前
【Linux系统编程】指令(三):创建、删除、复制、移动、看内容
linux·数据结构·c++·算法