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. 功能扩展:实现字典的模糊查询、新增、修改、删除功能,打造完整的字典管理系统。
相关推荐
QT 小鲜肉2 小时前
【Linux命令大全】001.文件管理之rcp命令(实操篇)
linux·服务器·网络·chrome·笔记
是垚不是土2 小时前
TDengine脚本备份方案:全库/单库备份与飞书通知
大数据·运维·数据库·飞书·时序数据库·tdengine
无言(* ̄(エ) ̄)2 小时前
C语言--运算符/函数/结构体/指针
c语言·开发语言·数据结构·数据库·算法·mongodb
代码游侠2 小时前
学习笔记——SQLite3 编程与 HTML 基础
网络·笔记·算法·sqlite·html
Tipriest_2 小时前
Linux 下开发 C/C++ 程序为什么头文件引用路径这么多和复杂
linux·c语言·c++
oMcLin2 小时前
Linux 容器技术实战:从 Docker 到 Podman 的无 root 权限部署
linux·docker·podman
IT_陈寒2 小时前
SpringBoot 3.2实战:我用这5个冷门特性将接口QPS提升了200%
前端·人工智能·后端
Tipriest_2 小时前
ubuntu快速查看一个apt包的描述信息和依赖等
linux·运维·ubuntu·apt
小流苏生2 小时前
当你不再热爱自己的工作和生活……
前端·程序员·ai编程