嵌入式数据库从入门到精通

在嵌入式Linux开发中,高效、轻量地管理数据是常见需求。SQLite作为一款开源、嵌入式的关系型数据库,无需独立的服务器进程,整个数据库就是一个文件,完美契合嵌入式场景。本文将带你从零开始,全面掌握SQLite在C语言环境下的使用。

SQLite简介与安装

SQLite是一个遵守ACID(原子性、一致性、隔离性、持久性)的关系数据库管理系统,它包含在一个相对小的C语言库中。与许多其他数据库管理系统不同,SQLite不是一个客户端/服务器结构的数据库引擎,而是被集成在用户程序中。

在Ubuntu等基于Debian的Linux系统上,安装SQLite非常简单。首先确保你的虚拟机网络连通且apt-get源已正确配置,然后执行以下命令:

cpp 复制代码
# 安装SQLite3命令行工具、开发库和图形化浏览器
sudo apt-get install sqlite3
sudo apt-get install libsqlite3-dev
sudo apt-get install sqlitebrowser

安装完成后,在终端输入sqlite3即可进入交互式命令行环境,输入.quit退出。

SQLite基础操作与SQL语法

常用SQLite命令

在sqlite3命令行环境中,点号(.)开头的命令用于管理数据库本身:

  • .databases:显示当前打开的数据库文件。

  • .tables:列出当前数据库中的所有表。

  • .schema 表名:显示创建指定表的SQL语句。

  • .header on/ .header off:开启/关闭查询结果的列名显示。

  • .mode 格式:设置结果输出格式,如column(列模式)、list(列表模式)、csv等。

  • .quit:退出sqlite3。

核心SQL语句

SQL(Structured Query Language)是操作数据库的标准语言。以下是SQLite中最常用的SQL语句:

  1. 创建表 (CREATE TABLE)

    定义表的结构,包括列名和数据类型。

    cpp 复制代码
    -- 创建一个学生表,包含学号、姓名、性别、年龄、成绩字段
    -- INTEGER PRIMARY KEY 表示学号是整数类型的主键(唯一且自增)
    -- TEXT 表示文本类型
    CREATE TABLE student (
        学号 INTEGER PRIMARY KEY,
        姓名 TEXT,
        性别 TEXT,
        年龄 INTEGER,
        成绩 INTEGER
    );
  2. 删除表 (DROP TABLE)

    永久删除一张表及其所有数据。

    复制代码
    DROP TABLE student;
  3. 插入数据 (INSERT INTO)

    向表中添加新的数据行。

    cpp 复制代码
    -- 为所有列插入值,值的顺序必须与表定义一致
    INSERT INTO student VALUES (1001, '张三', '男', 15, 90);
    -- 为指定列插入值
    INSERT INTO student (学号, 姓名, 性别) VALUES (1002, '李四', '女');
  4. 删除数据 (DELETE FROM)

    根据条件删除表中的数据行。

    cpp 复制代码
    -- 删除姓名为'王五'的学生记录
    DELETE FROM student WHERE 姓名='王五';
    -- 删除成绩小于60分的学生记录
    DELETE FROM student WHERE 成绩 < 60;
  5. 更新数据 (UPDATE)

    修改表中已存在的数据。

    cpp 复制代码
    -- 将张三的年龄更新为16岁,成绩更新为95分
    UPDATE student SET 年龄=16, 成绩=95 WHERE 姓名='张三';
  6. 查询数据 (SELECT)

    从表中检索数据,这是最常用、最灵活的语句。

    cpp 复制代码
    -- 查询student表中的所有列和所有行
    SELECT * FROM student;
    -- 只查询学号和成绩两列
    SELECT 学号, 成绩 FROM student;
    -- 查询成绩大于80分的学生姓名
    SELECT 姓名 FROM student WHERE 成绩 > 80;
    -- 查询所有姓'张'的学生(使用LIKE和通配符%)
    SELECT * FROM student WHERE 姓名 LIKE '张%';
    -- 查询结果按成绩降序排列(DESC: 降序, ASC: 升序)
    SELECT * FROM student ORDER BY 成绩 DESC;
  7. 多表联合查询

    当信息分散在多个表中时,需要联合查询。

    • 内连接 (INNER JOIN):返回两个表中匹配成功的记录。
    cpp 复制代码
    -- 假设有'学生信息'和'学生成绩'表,通过'学号'关联
    SELECT 学生信息.姓名, 学生成绩.成绩
    FROM 学生信息
    INNER JOIN 学生成绩 ON 学生信息.学号 = 学生成绩.学号;
    • 左外连接 (LEFT OUTER JOIN):返回左表所有记录,以及右表中匹配的记录,无匹配则为NULL。

      SELECT 学生信息.姓名, 学生成绩.成绩
      FROM 学生信息
      LEFT OUTER JOIN 学生成绩 ON 学生信息.学号 = 学生成绩.学号;

C语言编程操作SQLite数据库

要在C程序中使用SQLite,需要包含头文件#include <sqlite3.h>,并在编译时链接-lsqlite3库。

核心API函数

  1. 打开数据库:sqlite3_open

    此函数用于打开(或创建)一个数据库文件,并获得一个用于后续操作的数据库句柄。

    cpp 复制代码
    int sqlite3_open(
        const char *filename,   // 数据库文件路径 (UTF-8编码)
        sqlite3 **ppDb          // 输出参数:数据库句柄指针的指针
    );
    // 成功返回 SQLITE_OK (0),失败返回错误代码。
  2. 执行SQL语句:sqlite3_exec

    这是一个万能函数,用于执行不返回数据的SQL语句(如CREATE, INSERT, UPDATE, DELETE),或执行SELECT并提供一个回调函数处理结果。

    cpp 复制代码
    int sqlite3_exec(
        sqlite3* db,                               // 数据库句柄
        const char *sql,                           // 要执行的SQL语句字符串
        int (*callback)(void*,int,char**,char**),  // 回调函数(SELECT时用)
        void *arg,                                 // 传递给回调函数的参数
        char **errmsg                              // 错误信息指针
    );
    // 成功返回 SQLITE_OK。
  3. 错误信息:sqlite3_errmsg

    当API调用失败时,通过此函数获取可读的错误描述。

    cpp 复制代码
    const char *sqlite3_errmsg(sqlite3* db);
  4. 关闭数据库:sqlite3_close

    关闭数据库连接,释放资源。

    复制代码
    int sqlite3_close(sqlite3* db);
  5. 释放内存:sqlite3_free

    释放由SQLite库函数(如sqlite3_execerrmsg)分配的内存。

    复制代码
    void sqlite3_free(void* ptr);

实战示例:单词查询程序

下面我们实现一个简单的单词查询程序,模拟文档中提到的作业。程序打开一个包含单词表的数据库,并允许用户查询单词含义。

cpp 复制代码
#include <sqlite3.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>

typedef struct dict
{
    char name[32];
    char text[256];
}dict_t;

int pcallbackfun(void *parg, int columno, char **pcontents, char **pheaders)
{
    int i = 0;
    printf("=========================\n");
    for (i = 0; i < columno; i++)
    {
        printf("%s:%s\n", pheaders[i], pcontents[i]);
    }
    printf("=========================\n");

    return 0;
}

int main(void)
{
    int ret = 0;
    int i = 0;
    int found = 0;
    char *ptmp = 0;
    char word[32] = {0}; 
    char tmpbuff[256] = {0}; 
    char cmdbuf[1024] = {0}; 
    char *perrmsg = NULL;
    ssize_t nret = 0;
    FILE *fd = 0;
    dict_t wordlist = {0};
    sqlite3 *pdb = 0;

    //open
    fd = fopen("dict.txt", "r");
    if (fd == NULL) 
    {
        perror("fail to open");
        return -1;
    }

    //sqlite_open    
    ret = sqlite3_open("dict.db",&pdb);
    if(ret != SQLITE_OK)
    {
        perror("fail to sqlite3_open");
        return -1;
    }

    sprintf(cmdbuf, "create table if not exists dict (name text,text1 text);");
    ret = sqlite3_exec(pdb, cmdbuf, NULL, NULL, &perrmsg);
    if (ret != SQLITE_OK)
    {
        fprintf(stderr, "fail to sqlite3_exec:%s\n", perrmsg);
        sqlite3_free(perrmsg);                                      //释放空间
        sqlite3_close(pdb);
        return -1;
    }

    //read
    //sqlite_exec
    while(1)
    {
        char *pname = 0;
        char *ptext = 0;
        memset(wordlist.name,0,sizeof(wordlist.name));
        memset(wordlist.text,0,sizeof(wordlist.text));
        ptmp = fgets(tmpbuff, sizeof(tmpbuff), fd);
        if(NULL == ptmp)
        {
            break;
        }
        pname = tmpbuff;
        while(*pname != ' ')
        {
            pname++;
        }
        *pname = '\0';
        ptext = pname + 1;
        while(*ptext == ' ')
        {
            ptext++;
        }
        strncpy(wordlist.name, tmpbuff, sizeof(wordlist.name)-1);
        wordlist.name[sizeof(wordlist.name)-1] = '\0';  // 确保终止
        strcpy(wordlist.text,ptext);

        sprintf(cmdbuf, "insert into dict values(\"%s\", \"%s\");", wordlist.name, wordlist.text);
        ret = sqlite3_exec(pdb, cmdbuf, NULL, NULL, &perrmsg);
        if (ret != SQLITE_OK)
        {
            fprintf(stderr, "fail to sqlite3_exec:%s\n", perrmsg);
            sqlite3_free(perrmsg);
            sqlite3_close(pdb);
            return -1;
        }
    }
    printf("lll\n");
    gets(word);

    sprintf(cmdbuf, "select name,text1 from dict where name = '%s';", word);
    ret = sqlite3_exec(pdb, cmdbuf, pcallbackfun, NULL, &perrmsg);
    if (ret != SQLITE_OK)
    {
        fprintf(stderr, "fail to sqlite3_exec:%s\n", perrmsg);
        sqlite3_free(perrmsg);
        sqlite3_close(pdb);
        return -1;
    }
    //sqlite_close
    //close
    sqlite3_close(pdb);
    fclose(fd);

    return 0;
}

总结

SQLite以其零配置、无服务器、单文件的特性,成为嵌入式系统、移动应用和小型桌面应用的理想数据库选择。通过掌握基本的SQL语法和C语言API,你可以在你的嵌入式Linux项目中轻松实现数据持久化功能。记住,在实际开发中,务必使用参数化查询来防范SQL注入攻击,并注意及时关闭数据库连接、释放资源,以确保程序的健壮性和安全性。

相关推荐
原来是猿3 小时前
MYSQL【库操作】
数据库·mysql
爱吃羊的老虎3 小时前
【后端】MySQL 主从复制原理深度解析
数据库·mysql
blues92573 小时前
MySQL 篇 - Java 连接 MySQL 数据库并实现数据交互
java·数据库·mysql
虾..3 小时前
Linux 网络套接字编程
linux·运维·网络
曹牧3 小时前
C#:控制函数执行时间
数据库·c#
ApacheSeaTunnel3 小时前
(三)ODS/明细层落地设计要点:把数据接入层打造成“稳定可运维”的基础设施
数据库·数据仓库·数据湖·白鲸开源
猹叉叉(学习版)3 小时前
【ASP.NET CORE】 6. 中间件
数据库·笔记·后端·中间件·c#·asp.net·.netcore