ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux 数据库开发:从关系数据库到 SQLite3 C/C++ 接口编程

Linux 数据库开发:从关系数据库到 SQLite3 C/C++ 接口编程 1. 数据库与数据库管理系统数据库Database是按照数据结构来组织、存储和管理数据的仓库是长期存储在计算机内、有组织、可共享的数据集合。数据库中的数据具有较小的冗余度、较高的数据独立性和易扩展性并可为多种应用共享。数据库管理系统Database Management SystemDBMS是位于用户与操作系统之间的一层数据管理软件负责对数据库进行统一管理和控制。DBMS 的主要功能包括数据定义、数据操纵、数据库的运行管理、数据库的建立与维护等。常见的 DBMS 有 MySQL、Oracle、SQL Server、PostgreSQL 以及轻量级的 SQLite 等。2. 关系数据库关系数据库Relational Database是基于关系模型建立的数据库它使用二维表即关系来组织和存储数据。关系数据库是目前应用最广泛的数据库类型其理论基础是关系代数与关系演算。2.1 基本结构表Table是关系数据库中存储数据的基本结构由行记录和列字段组成。每一行代表一条记录每一列代表一个属性。例如一张学生表可以包含学号、姓名、年龄、专业等字段。2.2 系统类型关系型数据库系统按部署方式和使用场景可分为以下几类桌面型数据库如 SQLite、Access适合单机或嵌入式场景轻量易用。客户端/服务器型数据库如 MySQL、PostgreSQL、SQL Server适合中小型应用支持多用户并发访问。企业级大型数据库如 Oracle、DB2适合大型企业级应用功能强大、性能稳定。云数据库如阿里云 RDS、AWS RDS以云服务形式提供弹性伸缩、按需付费。3. ER 模型及范式ER 模型Entity-Relationship Model实体-联系模型由陈品山Peter Chen于 1976 年提出是数据库设计阶段用于描述现实世界信息结构的概念模型。ER 模型包含三个基本要素实体Entity、属性Attribute和联系Relationship。范式Normal Form是关系数据库设计中的规范化理论用于消除数据冗余和更新异常。常见的范式包括第一范式1NF每个属性都是不可再分的原子值。第二范式2NF在 1NF 基础上消除非主属性对码的部分函数依赖。第三范式3NF在 2NF 基础上消除非主属性对码的传递函数依赖。BCNF在 3NF 基础上消除主属性对码的部分和传递函数依赖。以「学生选课系统」为例这是一个典型的 ER 模型应用场景。系统中存在两个实体学生Student和课程Course。学生实体包含学号、姓名、专业等属性课程实体包含课程号、课程名、学分等属性。学生与课程之间存在「选修」联系这是一个多对多M:N联系因为一个学生可以选修多门课程一门课程也可以被多个学生选修。该联系本身还可以带有属性如成绩。在 ER 图中实体用矩形表示属性用椭圆表示联系用菱形表示。通过 ER 模型可以清晰地描述业务数据结构为后续转换为关系模式即表结构提供依据。4. 数据库语言关系型数据库语言是用于定义、操纵和控制数据库的语言主要分为以下几类数据定义语言DDL用于定义数据库结构包括创建、修改、删除表和索引等如 CREATE、ALTER、DROP。数据操纵语言DML用于对数据库中的数据进行增、删、改、查操作如 INSERT、DELETE、UPDATE、SELECT。数据控制语言DCL用于控制数据库的访问权限如 GRANT、REVOKE。5. 数据、数据类型、约束数据是数据库中存储的基本对象是对客观事物特征的符号记录。在关系数据库中数据以表的形式组织每个字段都有明确的数据类型。5.1 数据类型常见的数据类型包括整数类型INT、INTEGER、SMALLINT、BIGINT 等。浮点类型FLOAT、DOUBLE、REAL 等。字符类型CHAR、VARCHAR、TEXT 等。日期时间类型DATE、TIME、DATETIME、TIMESTAMP 等。二进制类型BLOB、BINARY 等。5.2 约束及分类约束Constraint用于保证数据的完整性和一致性主要分为以下几类主键约束PRIMARY KEY唯一标识表中的每一行且不能为空。唯一约束UNIQUE保证列中的值不重复。非空约束NOT NULL保证列的值不能为空。默认值约束DEFAULT为列指定默认值。检查约束CHECK限制列中值的取值范围。外键约束FOREIGN KEY用于建立表与表之间的关联保证引用完整性。6. 关系表创建CREATE TABLE 语句用于在数据库中创建新表其基本语法如下CREATE TABLE 表名 ( 列名1 数据类型 [约束], 列名2 数据类型 [约束], ... [表级约束] );示例创建一个学生表。CREATE TABLE student ( id INTEGER PRIMARY KEY, name VARCHAR(50) NOT NULL, age INTEGER CHECK (age 0), major VARCHAR(100) DEFAULT 未定 );7. 关系表基本操作关系表的基本操作包括插入、删除、修改和查询对应 SQL 中的 INSERT、DELETE、UPDATE 和 SELECT 语句。7.1 INSERTINSERT INTO 表名 (列名1, 列名2, ...) VALUES (值1, 值2, ...);示例INSERT INTO student (id, name, age, major) VALUES (1, 张三, 20, 计算机);7.2 DELETEDELETE FROM 表名 WHERE 条件;示例DELETE FROM student WHERE id 1;7.3 UPDATEUPDATE 表名 SET 列名 新值 WHERE 条件;示例UPDATE student SET age 21 WHERE id 1;7.4 SELECTSELECT 列名1, 列名2 FROM 表名 WHERE 条件;示例SELECT name, age FROM student WHERE major 计算机;8. SQLite3 C/C 接口在使用 SQLite3 的 C/C 接口之前需要先理解两个核心对象sqlite3和sqlite3_stmt。它们是整个 SQLite3 编程模型的基础几乎所有接口函数都围绕这两个对象展开。sqlite3是数据库连接句柄Database Connection Handle代表一个已打开或创建的数据库连接。它由sqlite3_open创建由sqlite3_close销毁。几乎所有数据库级操作如执行 SQL、准备语句、获取错误信息等都需要以 sqlite3 对象作为第一个参数传入。一个进程可以同时打开多个 sqlite3 对象分别对应不同的数据库文件。sqlite3_stmt是预编译语句对象Prepared Statement代表一条经过编译的 SQL 语句。它由sqlite3_prepare_v2创建由sqlite3_finalize销毁。sqlite3_stmt 对象用于参数绑定、逐步执行和结果读取是执行带参数查询和高效重复执行同一 SQL 的关键。一个 sqlite3 连接可以同时创建多个 sqlite3_stmt 对象。两个对象的关系可以概括为sqlite3 是「数据库连接」sqlite3_stmt 是「编译后的 SQL 语句」。前者负责管理连接和事务后者负责具体的语句执行与结果获取。二者配合使用构成了 SQLite3 C/C 编程的核心流程。SQLite 是一个轻量级的嵌入式关系数据库在 Linux 开发中应用广泛。SQLite3 提供了丰富的 C/C 接口相关函数如下。8.1 sqlite3_openint sqlite3_open(const char *filename, sqlite3 **ppDb);功能打开或创建数据库文件。参数filename 为数据库文件名ppDb 为输出参数指向数据库句柄指针。返回值成功返回 SQLITE_OK0失败返回错误码。8.2 sqlite3_closeint sqlite3_close(sqlite3 *db);功能关闭数据库连接释放资源。参数db 为数据库句柄。返回值成功返回 SQLITE_OK失败返回错误码。8.3 sqlite3_prepare_v2int sqlite3_prepare_v2(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail);功能将 SQL 语句编译为预编译语句对象statement用于后续的参数绑定和逐步执行。参数db 为数据库句柄zSql 为 SQL 语句nByte 为 SQL 语句长度传 -1 表示自动计算ppStmt 为输出参数指向预编译语句指针其类型为sqlite3_stmt **即指向sqlite3_stmt *指针的指针。调用前应先将*ppStmt初始化为 NULL函数成功时会在*ppStmt中写入新创建的sqlite3_stmt对象指针若失败则保持为 NULL。pzTail 为输出参数指向未使用的 SQL 部分。zSql 参数指向的原始 SQL 语句支持以下几种格式单条完整 SQL 语句如SELECT * FROM student;这是最常见的形式函数会将其编译为一个预编译语句对象。带参数占位符的 SQL 语句如SELECT * FROM student WHERE id ?;其中的?为参数占位符后续通过sqlite3_bind_text等函数绑定具体值。多条 SQL 语句拼接如SELECT * FROM student; SELECT * FROM course;此时函数只编译第一条语句剩余部分通过 pzTail 参数返回供后续再次调用sqlite3_prepare_v2继续编译。带注释的 SQL 语句如-- 查询学生信息\nSELECT * FROM student;SQLite 会忽略注释部分只编译有效的 SQL 语句。返回值成功返回 SQLITE_OK失败返回错误码。8.4 sqlite3_step函数原型int sqlite3_step(sqlite3_stmt *pStmt);功能执行预编译语句推进到下一行结果。该函数是 SQLite3 中执行语句的核心函数通常与sqlite3_prepare_v2配合使用先编译 SQL 语句再通过循环调用sqlite3_step逐行获取结果。参数pStmt 为预编译语句指针即sqlite3_stmt *类型指向由sqlite3_prepare_v2创建、且尚未被sqlite3_finalize销毁的语句对象。返回值返回类型为int其取值含义如下SQLITE_ROW100表示成功获取到一行结果。此时可通过sqlite3_column_text、sqlite3_column_int等函数读取当前行的列值然后继续调用sqlite3_step获取下一行。SQLITE_DONE101表示语句执行完成所有结果行已处理完毕或该语句本身不产生结果如 INSERT、UPDATE、DELETE。此时应调用sqlite3_finalize释放语句对象。SQLITE_BUSY5表示数据库文件被其他连接锁定无法执行当前操作。常见于多进程并发访问同一数据库时可通过重试或设置忙等待超时sqlite3_busy_timeout解决。SQLITE_ERROR1表示 SQL 语句执行过程中发生一般性错误如语法错误、约束冲突等。可通过sqlite3_errmsg获取具体错误信息。SQLITE_MISUSE21表示 API 被错误使用例如在未正确初始化或已销毁的语句对象上调用本函数或未按顺序调用接口。通常说明程序逻辑存在缺陷需要检查调用流程。SQLITE_DONE 之外的终止码如 SQLITE_ABORT、SQLITE_CONSTRAINT 等表示语句因特定原因被终止需结合具体错误码和sqlite3_errmsg定位问题。典型用法对于查询语句通常使用 while 循环反复调用sqlite3_step直到返回 SQLITE_DONE 为止对于非查询语句只需调用一次即可。示例代码如下while (sqlite3_step(stmt) SQLITE_ROW) { // 处理当前行数据 const unsigned char *name sqlite3_column_text(stmt, 0); printf(name %s\n, name); } // 循环结束后检查是否正常完成 if (sqlite3_step(stmt) ! SQLITE_DONE) { fprintf(stderr, 执行出错: %s\n, sqlite3_errmsg(db)); }查询结果分析函数在通过sqlite3_step获取到结果行后SQLite3 提供了一组用于分析当前结果行数据的函数用于获取列数、列名、列类型以及各列的具体值。这些函数通常与sqlite3_column_text配合使用构成完整的查询结果读取流程。sqlite3_column_countint sqlite3_column_count(sqlite3_stmt *pStmt);功能返回当前预编译语句结果集中的列数。参数pStmt 为预编译语句指针。返回值返回结果集的列数若语句尚未执行或没有结果集返回 0。sqlite3_column_nameconst char *sqlite3_column_name(sqlite3_stmt *pStmt, int iCol);功能获取指定列的列名。参数pStmt 为预编译语句指针iCol 为列索引从 0 开始。返回值返回该列的列名字符串若索引无效返回 NULL。sqlite3_column_typeint sqlite3_column_type(sqlite3_stmt *pStmt, int iCol);功能获取指定列的数据类型。参数pStmt 为预编译语句指针iCol 为列索引从 0 开始。返回值返回该列的数据类型取值包括 SQLITE_INTEGER1、SQLITE_FLOAT2、SQLITE_TEXT3、SQLITE_BLOB4和 SQLITE_NULL5。sqlite3_column_intint sqlite3_column_int(sqlite3_stmt *pStmt, int iCol);功能获取指定列的整数值。参数pStmt 为预编译语句指针iCol 为列索引从 0 开始。返回值返回该列的整数值若该列为 NULL返回 0。sqlite3_column_doubledouble sqlite3_column_double(sqlite3_stmt *pStmt, int iCol);功能获取指定列的浮点值。参数pStmt 为预编译语句指针iCol 为列索引从 0 开始。返回值返回该列的浮点值若该列为 NULL返回 0.0。sqlite3_column_bytesint sqlite3_column_bytes(sqlite3_stmt *pStmt, int iCol);功能获取指定列值的字节数对于 TEXT 类型为字符串长度对于 BLOB 类型为二进制数据长度。参数pStmt 为预编译语句指针iCol 为列索引从 0 开始。返回值返回该列值的字节数若该列为 NULL返回 0。8.5 sqlite3_column_text函数原型const unsigned char *sqlite3_column_text(sqlite3_stmt *pStmt, int iCol);功能获取当前结果行中指定列的文本值。该函数通常与sqlite3_step配合使用当sqlite3_step返回 SQLITE_ROW 时调用本函数读取当前行某一列的值并以字符串形式返回。参数pStmt预编译语句指针即sqlite3_stmt *类型指向由sqlite3_prepare_v2创建、且已通过sqlite3_step推进到有效结果行的语句对象。iCol列索引从 0 开始。0 表示第一列1 表示第二列依此类推。该索引对应 SELECT 语句中列的顺序例如SELECT name, age FROM student中name 的索引为 0age 的索引为 1。返回值返回该列文本值的指针类型为const unsigned char *指向以 \0 结尾的 UTF-8 字符串。若该列为 NULL则返回 NULL。返回的指针指向 SQLite 内部缓冲区在下次调用sqlite3_step、sqlite3_column_text或sqlite3_finalize之前有效如需长期保存应复制到自己的缓冲区。示例下面是一个完整的 C 语言示例演示如何查询学生表并读取 name 和 age 两列的值。#include stdio.h #include sqlite3.h int main(void) { sqlite3 *db; sqlite3_stmt *stmt; const char *sql SELECT name, age FROM student;; // 1. 打开数据库 if (sqlite3_open(test.db, amp;db) ! SQLITE_OK) { fprintf(stderr, 无法打开数据库: %s\n, sqlite3_errmsg(db)); return 1; } // 2. 编译 SQL 语句 if (sqlite3_prepare_v2(db, sql, -1, amp;stmt, NULL) ! SQLITE_OK) { fprintf(stderr, 编译失败: %s\n, sqlite3_errmsg(db)); sqlite3_close(db); return 1; } // 3. 逐步执行并读取结果 while (sqlite3_step(stmt) SQLITE_ROW) { const unsigned char *name sqlite3_column_text(stmt, 0); int age sqlite3_column_int(stmt, 1); printf(name %s, age %d\n, name, age); } // 4. 释放资源 sqlite3_finalize(stmt); sqlite3_close(db); return 0; }在上面的示例中sqlite3_column_text(stmt, 0)读取第一列 name 的文本值sqlite3_column_int(stmt, 1)读取第二列 age 的整数值。注意sqlite3_column_text返回的指针在下次调用sqlite3_step后可能失效因此示例中在循环体内立即使用并打印没有跨迭代保存。8.6 SQL 语句参数赋值8.6.1 获取参数索引在给 SQL 语句对象中的参数赋值之前通常需要先获取参数的索引。SQLite3 提供了两个辅助函数用于获取参数索引和参数个数int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName); int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt);sqlite3_bind_parameter_index功能根据参数名称获取对应参数的索引。当 SQL 语句中使用命名参数如:name、name、$name时可以通过该函数将参数名转换为数字索引供后续绑定函数使用。参数pStmt 为预编译语句指针zName 为参数名称字符串如:name。返回值返回参数对应的索引从 1 开始若未找到该名称的参数返回 0。sqlite3_bind_parameter_count功能返回预编译语句中参数占位符的总个数。参数pStmt 为预编译语句指针。返回值返回参数占位符的个数若语句中没有参数返回 0。8.6.2 参数赋值sqlite3_bind_text函数原型int sqlite3_bind_text(sqlite3_stmt *pStmt, int i, const char *zData, int nData, void (*xDel)(void*));功能为预编译语句中的参数? 占位符绑定文本值。这是给 SQL 语句对象中的参数赋值最常用的函数之一。参数pStmt 为预编译语句指针i 为参数索引从 1 开始可通过sqlite3_bind_parameter_index获取zData 为要绑定的文本nData 为文本长度传 -1 表示自动计算xDel 为析构回调通常传 SQLITE_STATIC 或 SQLITE_TRANSIENT。返回值成功返回 SQLITE_OK失败返回错误码。除了sqlite3_bind_text之外SQLite3 还提供了针对不同数据类型的绑定函数用于给参数赋值sqlite3_bind_intint sqlite3_bind_int(sqlite3_stmt *pStmt, int i, int value);为参数绑定整数值。sqlite3_bind_int64int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite3_int64 value);为参数绑定 64 位整数值。sqlite3_bind_doubleint sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double value);为参数绑定浮点值。sqlite3_bind_blobint sqlite3_bind_blob(sqlite3_stmt *pStmt, int i, const void *zData, int nData, void (*xDel)(void*));为参数绑定二进制数据。sqlite3_bind_nullint sqlite3_bind_null(sqlite3_stmt *pStmt, int i);为参数绑定 NULL 值。这些绑定函数的参数含义与sqlite3_bind_text基本一致pStmt 为预编译语句指针i 为参数索引从 1 开始value/zData 为要绑定的值nData 为数据长度xDel 为析构回调。返回值均为成功返回 SQLITE_OK失败返回错误码。8.7 sqlite3_reset函数原型int sqlite3_reset(sqlite3_stmt *pStmt);功能重置预编译语句对象使其回到初始状态以便重新执行。该函数不会改变语句的编译结果也不会清除已绑定的参数值只是将语句的执行状态恢复到刚由sqlite3_prepare_v2创建时的状态从而可以再次调用sqlite3_step从头执行。参数pStmt 为预编译语句指针即sqlite3_stmt *类型指向由sqlite3_prepare_v2创建、且尚未被sqlite3_finalize销毁的语句对象。返回值成功返回 SQLITE_OK0失败返回错误码。需要注意的是sqlite3_reset返回的是最近一次sqlite3_step执行的结果码而不是重置操作本身的错误码若重置成功通常返回 SQLITE_OK。典型用法当需要重复执行同一条 SQL 语句时可以在每次执行完毕后调用sqlite3_reset将语句对象复位再重新绑定参数并调用sqlite3_step从而避免反复调用sqlite3_prepare_v2带来的编译开销。示例代码如下// 编译一次重复执行多次 sqlite3_prepare_v2(db, INSERT INTO student (id, name) VALUES (?, ?), -1, stmt, NULL); for (int i 0; i 10; i) { sqlite3_bind_int(stmt, 1, i); sqlite3_bind_text(stmt, 2, 张三, -1, SQLITE_TRANSIENT); sqlite3_step(stmt); sqlite3_reset(stmt); // 复位语句准备下一次执行 } sqlite3_finalize(stmt);与 sqlite3_finalize 的区别sqlite3_reset只是重置语句的执行状态语句对象仍然存在并可继续使用而sqlite3_finalize会销毁语句对象并释放其占用的资源销毁后不能再使用。因此需要重复执行同一 SQL 时应使用sqlite3_reset不再需要该语句时才调用sqlite3_finalize。8.8 sqlite3_finalizeint sqlite3_finalize(sqlite3_stmt *pStmt);功能销毁预编译语句对象释放相关资源。参数pStmt 为预编译语句指针。返回值成功返回 SQLITE_OK失败返回错误码。8.9 sqlite3_exec函数原型int sqlite3_exec(sqlite3 *db, const char *sql, int (*callback)(void*, int, char**, char**), void *arg, char **errmsg);功能执行一条或多条 SQL 语句。该函数是 SQLite3 中最简单、最直接的执行接口适合执行不返回结果集的 SQL如 CREATE TABLE、INSERT、UPDATE、DELETE 等也可以执行查询语句并通过回调函数逐行处理结果。它内部封装了sqlite3_prepare_v2、sqlite3_step和sqlite3_finalize的完整流程使用起来非常方便。参数db数据库句柄即sqlite3 *类型指向由sqlite3_open打开的数据库连接。sql要执行的 SQL 语句字符串可以是单条语句也可以是用分号分隔的多条语句。多条语句会依次执行直到全部执行完毕或遇到错误为止。callback回调函数指针类型为int (*)(void*, int, char**, char**)。当执行查询语句且每获取到一行结果时SQLite 会调用该回调函数处理当前行数据若执行非查询语句或不需要处理结果可传 NULL。回调函数的四个参数含义如下arg由第 4 个参数 arg 传入的用户自定义数据原样传递给回调函数。nCol当前结果行的列数。colVals字符串数组存放当前行各列的值以文本形式表示。colNames字符串数组存放各列的列名。arg用户自定义数据指针会作为第一个参数传给回调函数通常用于传递上下文信息如结构体指针可为 NULL。errmsg输出参数指向char *的指针。若执行失败SQLite 会在*errmsg中写入错误信息字符串若成功*errmsg保持为 NULL。使用完毕后应调用sqlite3_free释放该字符串。若不需要错误信息可传 NULL。返回值成功返回 SQLITE_OK0失败返回错误码。常见的错误码包括 SQLITE_ERROR1、SQLITE_BUSY5、SQLITE_CONSTRAINT19等可通过sqlite3_errmsg获取具体错误描述。回调函数说明回调函数是sqlite3_exec处理查询结果的核心机制。每当 SQL 语句产生一行结果时SQLite 就会调用一次该回调函数将当前行的数据以参数形式传入。回调函数的完整原型如下int callback(void *arg, int nCol, char **colVals, char **colNames);回调函数的四个参数详细说明如下arg用户自定义数据指针。该值由sqlite3_exec的第 4 个参数 arg 原样传入不会做任何修改。通常用于向回调函数传递上下文信息例如结构体指针、计数器地址、输出文件句柄等。若不需要传递额外数据可传 NULL此时回调函数中的 arg 也为 NULL。nCol当前结果行的列数即该行包含多少个字段。它对应 SELECT 语句中查询的列数量例如SELECT id, name FROM student时 nCol 为 2。回调函数可通过它遍历 colVals 和 colNames 数组。colVals字符串数组存放当前行各列的值。数组长度为 nCol每个元素都是char *类型以文本形式表示该列的值。若某列为 NULL则对应元素为 NULL使用前需要判空。例如colVals[0]表示第一列的值colVals[1]表示第二列的值。colNames字符串数组存放各列的列名。数组长度同样为 nCol每个元素是char *类型表示对应列的列名。例如colNames[0]为第一列的列名colNames[1]为第二列的列名。通常用于打印表头或按列名定位数据。回调函数返回值回调函数返回 0 表示继续处理后续结果行返回非 0 值通常返回 1表示终止执行此时sqlite3_exec会立即返回 SQLITE_ABORT。因此若只需处理前几行数据可在回调函数中通过返回非 0 值提前终止查询。回调函数使用示例下面演示如何通过回调函数逐行打印查询结果并利用 arg 参数传递自定义计数器#include stdio.h #include sqlite3.h // 回调函数逐行打印查询结果并统计行数 int callback(void *arg, int nCol, char **colVals, char **colNames) { int *row_count (int *)arg; // 通过 arg 获取外部传入的计数器 (*row_count); // 每处理一行计数加 1 // 打印当前行的所有列列名 值 for (int i 0; i lt; nCol; i) { printf(%s %s\n, colNames[i], colVals[i] ? colVals[i] : NULL); } printf(---\n); return 0; // 返回 0 继续处理下一行 } int main(void) { sqlite3 *db; char *errmsg NULL; int row_count 0; // 用于统计结果行数 // 1. 打开数据库 if (sqlite3_open(test.db, amp;db) ! SQLITE_OK) { fprintf(stderr, 无法打开数据库: %s\n, sqlite3_errmsg(db)); return 1; } // 2. 执行查询语句通过回调函数处理结果并传入计数器地址 const char *select_sql SELECT id, name FROM student;; if (sqlite3_exec(db, select_sql, callback, amp;row_count, amp;errmsg) ! SQLITE_OK) { fprintf(stderr, 查询失败: %s\n, errmsg); sqlite3_free(errmsg); sqlite3_close(db); return 1; } // 3. 打印统计结果 printf(共查询到 %d 行数据\n, row_count); // 4. 关闭数据库 sqlite3_close(db); return 0; }在上面的示例中arg参数传入的是row_count回调函数通过(int *)arg将其还原为计数器指针并累加从而在查询结束后统计出总行数。这体现了 arg 参数在回调函数与主程序之间传递数据的典型用法。典型用法对于不返回结果集的 SQL如建表、增删改直接调用sqlite3_exec并将 callback 传 NULL 即可对于查询语句需要提供回调函数逐行处理结果。示例代码如下#include stdio.h #include sqlite3.h // 回调函数逐行打印查询结果 int callback(void *arg, int nCol, char **colVals, char **colNames) { for (int i 0; i nCol; i) { printf(%s %s\n, colNames[i], colVals[i] ? colVals[i] : NULL); } printf(---\n); return 0; // 返回 0 继续处理下一行 } int main(void) { sqlite3 *db; char *errmsg NULL; // 1. 打开数据库 if (sqlite3_open(test.db, db) ! SQLITE_OK) { fprintf(stderr, 无法打开数据库: %s\n, sqlite3_errmsg(db)); return 1; } // 2. 执行建表语句无结果集callback 传 NULL const char *create_sql CREATE TABLE IF NOT EXISTS student (id INTEGER PRIMARY KEY, name TEXT);; if (sqlite3_exec(db, create_sql, NULL, NULL, errmsg) ! SQLITE_OK) { fprintf(stderr, 建表失败: %s\n, errmsg); sqlite3_free(errmsg); sqlite3_close(db); return 1; } // 3. 执行插入语句无结果集 const char *insert_sql INSERT INTO student (id, name) VALUES (1, 张三);; if (sqlite3_exec(db, insert_sql, NULL, NULL, errmsg) ! SQLITE_OK) { fprintf(stderr, 插入失败: %s\n, errmsg); sqlite3_free(errmsg); sqlite3_close(db); return 1; } // 4. 执行查询语句有结果集通过回调处理 const char *select_sql SELECT * FROM student;; if (sqlite3_exec(db, select_sql, callback, NULL, errmsg) ! SQLITE_OK) { fprintf(stderr, 查询失败: %s\n, errmsg); sqlite3_free(errmsg); sqlite3_close(db); return 1; } // 5. 关闭数据库 sqlite3_close(db); return 0; }与 sqlite3_prepare_v2 sqlite3_step 的区别sqlite3_exec适合一次性执行简单 SQL代码简洁但无法使用参数绑定且每次执行都会重新编译 SQL性能较低而sqlite3_prepare_v2sqlite3_step支持参数绑定、可重复执行同一语句适合需要高效执行或处理动态参数的场景。对于需要读取结果并逐列获取类型化数据的复杂查询推荐使用sqlite3_prepare_v2sqlite3_stepsqlite3_column_xxx的组合。9. 常见错误错误处理是 SQLite3 编程中不可忽视的环节。在实际开发中几乎每个接口调用都可能失败因此养成「调用后立即检查返回值」的习惯至关重要。本文涉及的 8 个核心接口函数中sqlite3_open、sqlite3_prepare_v2、sqlite3_step、sqlite3_reset、sqlite3_finalize和sqlite3_exec均返回int类型的结果码成功时返回SQLITE_OK0失败时返回对应的错误码。通过检查返回值并结合sqlite3_errmsg获取错误描述可以快速定位问题。常见的错误码及含义SQLITE_OK0操作成功。SQLITE_ERROR1SQL 语法错误或逻辑错误如关键字拼写错误、表或列不存在、约束冲突等。可通过sqlite3_errmsg获取具体描述。SQLITE_BUSY5数据库文件被其他连接锁定常见于多进程并发访问。可通过重试或设置sqlite3_busy_timeout解决。SQLITE_CONSTRAINT19违反约束条件如主键重复、外键引用不存在等。SQLITE_NOMEM7内存不足无法完成操作。SQLITE_MISUSE21API 被错误使用如在未初始化或已销毁的对象上调用接口。推荐的错误处理模式在每次调用返回int的 SQLite3 接口后立即检查返回值是否等于SQLITE_OK。若失败通过sqlite3_errmsg(db)获取错误信息并打印或记录然后根据错误类型决定是重试、回滚事务还是退出程序。对于sqlite3_exec还需注意使用完毕后调用sqlite3_free释放errmsg指向的错误信息字符串。掌握这一套错误处理流程可以显著提升程序的健壮性和可维护性。10. 总结本文系统介绍了 Linux 开发中数据库编程的基础知识内容涵盖数据库与数据库管理系统、关系数据库、ER 模型与范式、关系型数据库语言、数据类型与约束、CREATE TABLE 语法、增删改查语法以及 SQLite3 的 8 个核心 C/C 接口函数。在数据库基础部分我们理解了数据库与数据库管理系统DBMS的概念掌握了关系数据库以二维表组织数据的基本思想并通过 ER 模型与范式了解了数据库设计阶段的概念建模与规范化方法。关系型数据库语言中的 DDL 与 DML 分别负责结构定义和数据操纵为后续的表操作打下基础。在表操作部分我们学习了 CREATE TABLE 的建表语法以及 INSERT、DELETE、UPDATE、SELECT 四种基本操作能够完成对关系表的增删改查。在 SQLite3 C/C 接口部分我们重点掌握了两个核心对象sqlite3数据库连接句柄和sqlite3_stmt预编译语句对象并系统学习了 8 个核心接口函数sqlite3_open、sqlite3_close、sqlite3_prepare_v2、sqlite3_step、sqlite3_column_text、sqlite3_reset、sqlite3_finalize和sqlite3_exec。其中sqlite3_prepare_v2与sqlite3_step配合可实现参数绑定与逐行结果读取sqlite3_exec则适合一次性执行简单 SQL二者各有适用场景。掌握这些内容可以为后续在 Linux 环境下进行数据库应用开发打下坚实基础。建议读者结合示例代码动手实践逐步熟悉 SQLite3 的编程流程与接口细节。
返回列表