数据库(sqlite)
一:简介:
为什么需要单独的数据库来进行管理数据?
- 数据的各种查询功能
- 数据的备份和恢复
- 花大量时间在文件数据的结构设计和维护上
- 要考虑多线程对数据的操作会涉及到同步问题,会增加很多额外工作
sqlite
(1)概念
SQLite是一个开源的轻量级关系数据库管理系统,它的不同之处在于它是以库的形式提供的,这意味着它是一个零配置、无需安装和管理的数据库引擎(一个Sqlite数据库,就是一个单独的文件)。它不是一个独立的服务进程,而是直接集成到应用程序中。SQLite存储整个数据库(定义、表、索引和数据)为单个跨平台的文件(.db文件),在多个系统和程序语言之间提供极致的可移植性。
注意:sqlite所有操作都是在本地直接通过对数据库文件的读写来完成的
二:sqlite安装(linux下)
1、 安装sqlite3
sudo apt install sqlite3
3、退出数据库操作终端命令
ctrl +d
或
输入 .quit 或 .exit
4、关于数据库的一些核心概念
(1)数据库
- 安装的sqlite3本身是一个数据库软件,通过该软件可以创建和管理多个以二维表作为数据存储手段的数据库,比如学校数据库、迷宫数据库、电影院数据库
(2)表(二维表)
概念:二维表(简称表)是保存某一类数据的具体载体。一般一个数据库可以有多张二维表,每张二维表可以存储一类的数据。比如学生表(保存学生类的数据,一行数据就是一个学生),成绩表、老师表。
例子:迷宫数据库
应该有那些表
buff表
敌人表
地图表
地图编号 row_index col_index value 1 0 0 1 1 0 1 1 1 0 2 1 省略了17行 1 1 0 1 1 1 1 3 1 1 2 0 玩家表:
玩家编号 玩家名称 x y buff表 步数 地图编号 1 张三 12 13 23 1 2 李四 15 3 45 3
(3)一行数据
- 我们一般会将一张表的一行作为完整的一个数据。
三:表的操作
1、创建一张表
语法:
CREATE TABLE 二维表名{
列1名称 数据类型 该列的特点,
列2名称 数据类型 该列的特点,
...
列n名称 数据类型 该列的特点
};
-- 表的最后一列末尾不加逗号
例子:构建一张学生表
create table student (
id integer primary key autoincrement,
name text not null,
age integer,
email text not null unique
);
-- 创建带时间的表
CREATE TABLE events (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 插入带时间的数据
INSERT INTO events (name, created_at) VALUES ('Event 2', DATETIME('now'));
其中:id为列的名称,primary key表示该列为主要列,一张表最多只有一列是主要列。一般表示该列是用于区分每一行内容的。比如每个人都有身份证号码,那么该号码就可以作为人这张表的primary key列。AUTOINCREMENT
表示该列自动填充,从1开始依次递增。也就是第一行内容id为1,第二行内容id为2。INTEGER
表示数据类型。数据库有很多类型。但大家只需要关注INTEGER TEXT REAL
整数、字符串、浮点数三种数据类型即可。数据类型和关键字不区分大小写。这几种只有浮点数确定是8个字节,其他数据内存大小是由数据的本身大小来决定的。
每列的特点(都是可选,根据实际需求来定):
**PRIMARY KEY:**将该列作为该表的主要参考列(简称主键),一般我们会将编号列作为主键。该列的每一个数据不能重复,一般会配合AUTOINCREMENT
来使用,会自动在给表插入新数据时,该列编号自动填写并为上一个数据编号+1
NOT NULL 表示该列数据不能为空。
UNIQUE表示该列数据不能出现重复的。
DEFAULT 数据: 表示该列可以有一个默认数据,比如default 0 表示默认数据为0,前提是数据类型是 数字,如果是文本则需要加一对引号
2、列出所有表
SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;
注意:
(1)sqlite-master
sqlite_master
是SQLite数据库中的一个内置表,每个SQLite数据库文件都包含这个表。sqlite_master
表存储了数据库的元数据,包括数据库中存在的所有表格、索引、视图以及触发器的定义信息。通常不需要更改这张表。也可以用sql语句来查看这张表里面的内容。比如SELECT type, name, sql FROM sqlite_master WHERE type='table';
sqlite_master
表的结构大概如下:
type
: 对象的类型(例如,‘table’、‘index’、‘view’、‘trigger’)。name
: 对象的名称。tbl_name
: 对象相关联的表的名称(对于表本身,name
和tbl_name
字段将相同)。rootpage
: 对象在数据库文件中的起始页码。sql
: 创建该对象的原始SQL语句。
(2)sqlite_sequence
在SQLite数据库中,sqlite_sequence
是一个内部系统表,用于跟踪自动增长(AUTOINCREMENT)字段的当前最大值。当你在表定义中为某个列使用了INTEGER PRIMARY KEY AUTOINCREMENT
属性时,SQLite会使用sqlite_sequence
表来存储该列最近分配的最大整数值。这样,当新记录被插入到表中时,SQLite就可以为带有AUTOINCREMENT
属性的列自动分配一个比之前所有记录都大的唯一标识符。
sqlite_sequence
表有两个字段:
- name:这一列存储的是表的名称,该表含有一个
AUTOINCREMENT
字段。 - seq:这一列存储的是该表中最后一个被分配的
AUTOINCREMENT
值。
3、查看表结构
PRAGMA table_info(表名);
或
// 只列出自己表
sqlite> .tables
s2 student
4、删除表
DROP TABLE IF EXISTS 表名;
四:使用工具连接
SqliteBrowser
sudo apt-get install libsqlite3-dev
step3:安装Sqlite3图形化管理界面DB Browser for SQLite(用户可选择性安装):
sudo apt-get install sqlitebrowser
step4:检查是否安装成功,如果成功,输入以下命令可显示SQLite3版本信息。
五:数据的操作
sqlite推荐用单引号来包围字符串,双引号在不同版本中会有冲突
1:增加数据
-- 新增数据
-- INSERT INTO student (id, name, age, gender) values (2,'tom',26,'male')
INSERT INTO student ( name, age, gender) values ( 'lili',25,'female');
-- INSERT INTO student values ( 5, 'lili',22,'female')
-- Result: table student has 4 columns but 3 values were supplied
2:修改数据
-- 修改数据:
-- UPDATE student SET name='张三', age=50, gender = '女' WHERE id = 8
-- UPDATE student SET age=18 WHERE id = 1 or id = 3 or id = 5
-- UPDATE student SET gender='男' WHERE id = 1 and id = 3
-- 可以匹配多个id
-- UPDATE student SET age=3 WHERE id in (1,3,5, 11)
3:删除数据
-- 删除数据
-- 同样的,条件是可以跟多个的,也可以用or 或 and
-- delete from student where id = 3
-- 不跟条件,是清空表里的数据
-- delete from student
4:查找数据
-- 删除数据
-- delete from student where id = 3
-- 不跟条件,是清空表里的数据
-- delete from student
-- 查询操作:
-- * 代表显示所有的列
-- select * from student
-- select id, name , age, gender from student
-- 查询指定id的数据
--select * from student where id in (1,3,5,7,9);
-- 范围查询:
-- select * from student where age >= 20 and age <= 25
-- select * from student where age between 20 and 25
-- NULL != NULL
-- select * from student where gender is NULL
-- 查询结果,可以再处理,也可以使用别名
-- select id as "学号" , name as 名字, age+10 年龄 from student where gender is not NULL
-- 模糊查询:
/*
_ 代表任意一个字符:
% 任意个,任意字符
*/
-- select * from student where name like '%强%'
-- 查询含有特殊字符的数据,需要使用到转义符
-- select * from student where name like '%\_%' ESCAPE '\'
-- 分页查询:
-- select * from student limit 0, 3 -- 第一页
-- select * from student limit 3, 3 -- 第二页
-- select * from student limit 6, 3 -- 第三页
/*
limit x, y;
当前页:currentPage = 2
页大小:pageSize = 3
x = (currentPage-1)*pageSzie;
y = pageSize;
*/
练习:
1:分别查询出男生的id,名字和女生的id,名字
2:查询年龄大于20岁的人员信息
3:查询出年龄在15-25之间的,女性的信息
4:查询出id是 1,3, 5, 6,8 男性人员的信息
5:查询出,联系电话为空的人员的信息
6:查询出,所有的名字带 ‘丽’ 的人员的信息
- 答案:
select id, name from student where gender = 'F'
select * from student where age > 20
select * from student where age between 15 and 25 and gender = 'female';
select * from student where gender = 'male' and id in (1,3,5,6,8)
select * from student where tel is NULL;
select * from student where name like '%丽%'
- 查询的结果排序:
-- 排序规则默认是:asc 数字是从小到大,字母是字典顺序 desc 是倒序
-- select * from student order by name asc
-- 排序的规则后面,可以跟多个字段。
select * from student order by age desc, name desc
练习:
找出年龄最大的前3个学生的信息:
找出最大的年龄
select * from student order by age desc limit 0, 3
select age from student order by age desc limit 0, 1
六:常见组函数:
-- 求最大值
-- select max(age) from student
-- 求最小值
-- select min(age) from student
-- 求平均数
-- select avg(age) from student
-- 计数
-- select count(id) from student
-- 求总和
select sum(age) from student
七:C 程序中使用sqlite3
1:使用步骤
- 安装:使用
sudo apt install libsqlite3-dev
让Linux安装sqlite3开发库 - 引入:c文件引入
sqlite3.h
头文件 - 打开数据库:使用
sqlite3_open
函数打开数据库 - 执行:使用
sqlite3_exec
来执行sql语句,比如创建表、增删查改数据等 - 关闭数据库:使用
sqlite3_close
来关闭数据库 - 编译:代码写好后进行gcc编译时,需要加上
-lsqlite3
参数。比如gcc xxx.c -o sqldemo -lsqlite3
2:操作函数
api大全:https://sqlite.readdevdocs.com/cintro.html
(1)sqlite3_open
语法
int sqlite3_open(const char *filename, sqlite3 **ppDb); 例子: sqlite3 *db; //链接数据库 int rc = sqlite3_open("stu.db",&db);
功能:链接数据库
参数
filename
: 数据库文件的名称。如果文件不存在,SQLite 会尝试创建它。ppDb
: 是指向sqlite3
结构体指针的指针。当函数执行成功后,这个指针会被设置为指向一个打开的数据库实例
返回值
- 函数返回一个整数状态码。
SQLITE_OK
(0) 表示成功,其他任何返回值都表示失败
- 函数返回一个整数状态码。
(2)sqlite3_exec
功能:执行sql语句
语法-
int sqlite3_exec( sqlite3*, /* 打开的数据库指针 */ const char *sql, /* sql语句 */ int (*callback)(void*,int,char**,char**), /* 回调函数:返回查询结果 */ void *pArg, /* 传递给回调函数的参数,可以设置为NULL */ char **errmsg /* 错误信息 */ ); /* 回调函数说明:*/ sqlite3_callback 通常指的是传递给 sqlite3_exec() 函数的第三个参数,这是一个用户定义的回调函数。当 sqlite3_exec() 执行一个 SQL 查询(如 SELECT 语句)并返回结果行时,它会调用这个回调函数来处理每一行的数据。 回调函数的签名是固定的,以便 SQLite 能够正确地调用它。这个签名通常如下: typedef int (*sqlite3_callback)( void*, /* 用户提供的上下文指针,对应于 sqlite3_exec() 的第四个参数 */ int, /* 结果集中的列数 */ char**, /* 指向结果行中各列数据的指针数组 */ char** /* 指向结果行中各列名称的指针数组 */ )
参数
- 第一个参数是一个打开的数据库实例,之前由
sqlite3_open
打开。 - 第二个:
sql
是要执行的SQL语句字符串。可以包含一个或多个由分号分隔的SQL命令。 - 第三个:callback是一个指向函数的指针,这个函数会被每一行结果调用一次。如果不需要处理结果集,则可以为NULL。
- 该回调函数的签名应该符合
int callback(void *NotUsed, int argc, char **argv, char **azColName)
,其中:NotUsed
是sqlite3_exec中用户提供的第四个参数。argc
表示结果集中的列数。argv
是一个字符串数组,包含每一列的值。azColName
是一个字符串数组,包含每一列的名称。
- 该回调函数的签名应该符合
- 第四个参数: pArg
- 这是一个用户提供的上下文指针,它将被传递给回调函数(如果提供了回调函数的话)。
- 您可以使用这个指针来传递任何您希望在回调函数中访问的数据或状态信息。
- 如果不需要传递任何上下文信息给回调函数,可以将此参数设置为
NULL
。
errmsg
如果不为NULL,任何错误信息都会被写入到它指向的位置。使用sqlite3_free()
来释放这个错误信息的内存
- 第一个参数是一个打开的数据库实例,之前由
返回值
- 返回
SQLITE_OK
(0) 表示成功,其他值表示失败。失败时,如果errmsg
不为NULL,它将指向一个错误消息
- 返回
(3)sqlite3_free
功能:释放内存,一般指sql语句执行后分配给errmsg的内存。
语法
void sqlite3_free(void*); 例子:sqlite3_free(err_message); //err_message 是执行sql失败之后的错误信息
参数
- 接受一个指针,该指针必须是由sqlite分配的内存块的指针,通常是在之前的
sqlite3_exec
调用中通过errmsg
返回的错误消息.如果sql语句执行成功,一般不用执行该函数
- 接受一个指针,该指针必须是由sqlite分配的内存块的指针,通常是在之前的
返回值:无
(4)sqlite3_close
功能:关闭一个打开的数据库连接
语法
int sqlite3_close(sqlite3*); 例子: sqlite3_close(db);
参数
- 接受一个指向
sqlite3
结构体的指针,这个指针代表一个打开的数据库实例
- 接受一个指向
返回值
- 如果成功关闭数据库,返回
SQLITE_OK
。如果作为参数传递给sqlite3_close
的数据库实例有尚未完成的操作(例如,未完成的准备语句、未关闭的BLOB句柄等),则返回SQLITE_BUSY
- 如果成功关闭数据库,返回
(4)sqlite3_changes
功能:得到数据库连接上次调用
sqlite3_exec
以来被修改、插入或删除的行数语法
int sqlite3_changes(db);
参数
- db:接受一个指向
sqlite3
结构体的指针,这个指针代表一个打开的数据库实例
- db:接受一个指向
返回值
- 得到数据库连接上次调用
sqlite3_exec
以来被修改、插入或删除的行数,如果没有更改,则返回0
- 得到数据库连接上次调用
代码示例:新增数据:
#include <stdio.h> #include <stdbool.h> #include <string.h> #include <stdlib.h> #include <sqlite3.h> int main(int argc, char *argv[]) { //char sql[] = "insert into student (name,password, age , gender) values ('小丽丽','1234',19,'女')"; //char sql[] = "update student set age = 88 where id = 1 or id = 3"; char sql[] = "delete from student where id = 3"; // 1: 打开数据库 sqlite3 *db; //链接数据库 int rc = sqlite3_open("school.db",&db); if(rc != 0){ perror("数据库,打开失败!"); exit(EXIT_FAILURE); } char *errmsg = 0; // 2: 执行sql rc = sqlite3_exec( db, /* 打开的数据库指针 */ sql, /* sql语句 */ NULL, /* 回调函数:返回查询结果 */ NULL, /* 传递给回调函数的参数,可以设置为NULL */ &errmsg /* 错误信息 */ ); if(rc == SQLITE_OK ){ int rows = sqlite3_changes(db); printf("sql执行成功!影响数据行数:%d\n", rows); }else { // 打印错误信息: fprintf( stderr, "SQL错误:%s\n", errmsg ); sqlite3_free(errmsg); } // 3: 关闭数据库 sqlite3_close(db); return 0; }
查询数据:
#include <stdio.h> #include <stdbool.h> #include <string.h> #include <stdlib.h> #include <sqlite3.h> // 用来显示数据的回调函数 int showData(void*,int,char**,char**); int loginState = 0; bool login(char* name, char* password){ //char sql[] = "select * from student where name = '' and password = '' "; char sql[128]; sprintf( sql, "select * from student where name = '%s' and password = '%s' ", name, password ); // 1: 打开数据库 sqlite3 *db; //链接数据库 int rc = sqlite3_open("school.db",&db); if(rc != 0){ perror("数据库,打开失败!"); exit(EXIT_FAILURE); } char *errmsg = 0; // 2: 执行sql rc = sqlite3_exec( db, /* 打开的数据库指针 */ sql, /* sql语句 */ showData, /* 回调函数:返回查询结果 */ NULL, /* 传递给回调函数的参数,可以设置为NULL */ &errmsg /* 错误信息 */ ); if(rc == SQLITE_OK ){ printf("sql执行成功!\n"); }else { // 打印错误信息: fprintf( stderr, "SQL错误:%s\n", errmsg ); sqlite3_free(errmsg); } // 3: 关闭数据库 sqlite3_close(db); } int main(int argc, char *argv[]) { char name[] = "abcd"; char password[] = "' or '1'='1"; login(name, password); if(loginState){ puts("登录成功"); }else { puts("用户名或密码错误!"); } return 0; } int showData(void* no_used,int cols ,char** col_values ,char** col_names){ printf("总列数是:%d\n", cols); puts("--------------------------------"); for(int i = 0; i < cols; i++){ //loginState = 1; printf("列名:%s , 值:%s \n", col_names[i], col_values[i]); //return 0; } puts("--------------------------------"); return 0; }
这样使用字符串,拼接方式,得到的sql语句,有注入的风险。
我们应该避免这样的情况发生。
八:sql的进阶操作:
当我们以拼接字符中的方式创建SQL语句,其实是有SQL注入风险的。我们应该避免这种情况。
1:使用预编译,对sql进行处理:
语法说明:
sqlite3_prepare_v2() 参数说明:
int sqlite3_prepare_v2( sqlite3 *db, /* Database handle */ const char *zSql, /* SQL statement, UTF-8 encoded */ int nByte, /* Maximum length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: Statement handle */ const char **pzTail /* OUT: Pointer to unused portion of zSql */ );
sqlite3 *db
- 该参数为指向
sqlite3
类型的指针,代表了一个已经打开的 SQLite 数据库连接。在调用sqlite3_prepare_v2()
之前,你需要使用sqlite3_open()
或者sqlite3_open_v2()
函数来打开数据库,从而得到这个数据库连接句柄。只有借助有效的数据库连接,才能对相应的数据库开展操作。
- 该参数为指向
const char *zSql
- 此参数是一个指向以 null 结尾的字符串的指针,该字符串包含了要编译的 SQL 语句。这个 SQL 语句可以是
SELECT
、INSERT
、UPDATE
、DELETE
等各类 SQL 命令。需要注意的是,SQL 语句里可以包含参数占位符(如?
或者:name
),这有助于后续绑定具体的值。
- 此参数是一个指向以 null 结尾的字符串的指针,该字符串包含了要编译的 SQL 语句。这个 SQL 语句可以是
int nByte
- 该参数指定了
zSql
中 SQL 语句的最大字节数。若nByte
为负数,那么sqlite3_prepare_v2()
函数会把zSql
当作以 null 结尾的字符串来处理,直至遇到字符串结束符'\0'
为止。若nByte
为正数,函数会处理zSql
中的前nByte
个字节。
- 该参数指定了
sqlite3_stmt **ppStmt
- 这是一个指向
sqlite3_stmt
指针的指针。sqlite3_stmt
代表了一个准备好的语句对象,在调用sqlite3_prepare_v2()
之后,该函数会把编译好的准备好的语句对象的地址存储到*ppStmt
中。后续你可以使用这个准备好的语句对象来执行 SQL 语句、绑定参数等操作。在使用完毕后,要调用sqlite3_finalize()
函数来释放这个准备好的语句对象所占用的资源。
- 这是一个指向
const char **pzTail
- 这是一个指向指针的指针,用于返回 SQL 语句中未处理的部分。在
zSql
中可能包含多条 SQL 语句,当sqlite3_prepare_v2()
编译完第一条 SQL 语句后,会把剩余部分的起始地址存储到*pzTail
中。若zSql
中只有一条 SQL 语句,那么*pzTail
会指向 SQL 语句结束符'\0'
之后的位置
- 这是一个指向指针的指针,用于返回 SQL 语句中未处理的部分。在
参数详细说明:
const char *sql = "SELECT * FROM student where id = ? or name = ?"; // 1:对sql进行预编译 sqlite3_prepare_v2(db, /* 数据库连接句柄 */ sql, /* 输入的 SQL 语句 */ -1, /* SQL 语句的最大字节数 */ &stmt, /* 指向准备好的语句对象的指针 */ NULL); /* 指向 SQL 语句中未处理部分的指针 */ // 2:对sql中的?号占位符进行参数绑定 int id = 3; sqlite3_bind_int(stmt, 1, id); // 绑定int值 char name[] = "tom"; sqlite3_bind_text(stmt, 2 , name , -1, SQLITE_STATIC ); // 绑定字符串 /* sqlite3_bind_text 函数的参数说明: 1: sqlite3_stmt 变量 2: 占位符的位置,从1开始 3: 占位符对应的值 4: 字符串的长度,-1代表系统自动运算 5: 有三个取值: 5-1: SQLITE_STATIC :在预处理的整个生命周期内都是有效的,无需复制该值,因它知在执行期间该值不会变或释放 5-2: SQLITE_TRANSIENT :表示提供的值在语句执行期间有效,但可能在语句执行后被释放或修改。SQLite 通常会复制这个值以确保安全 5-3: SQLITE_DYNAMIC :表示值是通过malloc()获得的且SQLite在无需使用时该释放它。通常用于希望SQLite管理内存时 */ // 3:注意要使用 sqlite3_step(stmt); 执行sql ----------------- sqlite3_step(stmt);
sqlite3_step() 函数 返回值说明;
函数成功状态码:
- SQLITE_ROW(100):表示查询结果集中存在下一行数据。当执行 SELECT 语句时,如果有结果行,sqlite3_step 会返回此值,你可以通过 sqlite3_column_ 系列函数来获取该行数据的各个列值*。
- SQLITE_DONE(101):意味着 SQL 语句执行完毕。对于非查询语句(如 INSERT、UPDATE、DELETE),当操作完成时返回此值;对于查询语句,当遍历完所有结果行后也会返回此值。
错误状态码:
- 有很多,从1~29 代表各种不同的错误(详细看官网)
详解
sqlite3_step()
的SQLITE_ROW
和SQLITE_DONE
一、函数原型
int sqlite3_step(sqlite3_stmt* stmt);
二、返回值核心含义
返回值 含义 适用语句类型 SQLITE_ROW
当前有数据行可用(仅SELECT查询) SELECT 及其变种 SQLITE_DONE
语句执行完成(INSERT/UPDATE/DELETE成功,或SELECT结果集遍历结束) 所有SQL语句
三、
SQLITE_ROW
详解1. 触发条件
- SELECT查询结果集中还有未读取的行
2. 典型用法
sqlite3_stmt* stmt; sqlite3_prepare_v2(db, "SELECT id, name FROM users", -1, &stmt, NULL); // 循环读取每一行 while (sqlite3_step(stmt) == SQLITE_ROW) { int id = sqlite3_column_int(stmt, 0); // 读取第0列(id) const char* name = sqlite3_column_text(stmt, 1); // 读取第1列(name) printf("ID: %d, Name: %s\n", id, name); }
3. 关键特性
- 必须用
while
循环处理多行结果 - 每次返回
SQLITE_ROW
时,需要用sqlite3_column_*
函数读取当前行数据
四、
SQLITE_DONE
详解1. 触发场景
语句类型 含义 SELECT 结果集已全部遍历完成(没有更多数据行) INSERT/UPDATE/DELETE SQL语句执行成功,数据已修改 其他语句 语句执行完成(如CREATE TABLE) 2. 典型用法
场景1:判断写操作成功// INSERT示例 sqlite3_prepare_v2(db, "INSERT INTO logs(msg) VALUES('error')", -1, &stmt, NULL); if (sqlite3_step(stmt) == SQLITE_DONE) { printf("插入成功,影响行数:%d\n", sqlite3_changes(db)); } else { printf("失败:%s\n", sqlite3_errmsg(db)); }
场景2:SELECT结束检测
while (sqlite3_step(stmt) == SQLITE_ROW) { /* 读取数据 */ } // 循环退出时自动达到SQLITE_DONE状态 printf("查询完成\n");
五、对比案例
案例1:SELECT查询
sqlite3_stmt* stmt; sqlite3_prepare_v2(db, "SELECT * FROM products", -1, &stmt, NULL); int row_count = 0; while (sqlite3_step(stmt) == SQLITE_ROW) { printf("产品:%s\n", sqlite3_column_text(stmt, 1)); row_count++; } printf("共查询到%d条数据\n", row_count); // 循环退出时即SQLITE_DONE状态
案例2:INSERT操作
sqlite3_prepare_v2(db, "INSERT INTO orders VALUES(1001, 'book')", -1, &stmt, NULL); if (sqlite3_step(stmt) == SQLITE_DONE) { printf("订单创建成功!\n"); } else { printf("错误码:%d\n", sqlite3_errcode(db)); }
六、特殊注意事项
必须检查返回值
错误示例:sqlite3_step(stmt); // ❌ 不检查返回值可能导致未知错误
重置语句
如需复用语句对象,必须调用sqlite3_reset()
:sqlite3_reset(stmt); // 重置到可执行状态 sqlite3_clear_bindings(stmt); // 可选:清除绑定参数
多语句执行
批量执行SQL时,每次sqlite3_step()
只推进一个语句:// 执行多条SQL sqlite3_prepare_v2(db, "INSERT INTO t1 VALUES(1); INSERT INTO t2 VALUES(2);", ...); while (sqlite3_step(stmt) == SQLITE_ROW) {} // 忽略SELECT结果 if (sqlite3_step(stmt) == SQLITE_DONE) { printf("所有语句执行完成\n"); }
七、流程图解
八、总结
SQLITE_ROW
- SELECT查询的"数据就绪"信号
- 必须配套使用
sqlite3_column_*
读取数据 - 需要循环处理直到返回
SQLITE_DONE
SQLITE_DONE
- 所有语句的"执行完成"信号
- 写操作的成功标志
- SELECT查询的结束标志
详解
sqlite3_changes()
和sqlite3_errmsg()
1.
sqlite3_changes()
:获取受影响的行数作用:
返回最近一次INSERT
/UPDATE
/DELETE
操作影响的数据行数。函数原型:
int sqlite3_changes(sqlite3* db);
关键特性:
- 立即生效:只反映最近一次操作的修改量
- 作用范围:针对当前数据库连接(
sqlite3* db
) - 返回值:
- 正整数:实际受影响的行数
- 0:语句执行但未修改数据(如
UPDATE
条件不匹配)
典型用法:
// 执行UPDATE操作 sqlite3_exec(db, "UPDATE users SET status=1 WHERE age>18", NULL, NULL, NULL); // 获取影响行数 int rows_updated = sqlite3_changes(db); printf("更新了%d条用户数据\n", rows_updated);
注意事项:
- 不适用于
SELECT
语句(返回0) - 批量操作时只记录最后一条语句的影响
2.
sqlite3_errmsg()
:获取错误信息作用:
返回最近一次SQLite操作的错误描述(人类可读的字符串)。函数原型:
const char* sqlite3_errmsg(sqlite3* db);
关键特性:
- 错误覆盖:包括预处理、执行、约束违反等所有阶段错误
- 返回值:
- 错误描述字符串(如
"UNIQUE constraint failed"
) - 成功时为
"not an error"
- 错误描述字符串(如
典型用法:
if (sqlite3_step(stmt) != SQLITE_DONE) { printf("操作失败:%s\n", sqlite3_errmsg(db)); // 输出示例:"operation failed: UNIQUE constraint failed: users.email" }
搭配函数:
int errcode = sqlite3_errcode(db); // 获取错误码(如SQLITE_CONSTRAINT)
3. 对比总结
函数 作用 适用场景 返回值类型 sqlite3_changes()
获取写操作影响行数 INSERT/UPDATE/DELETE 整数 sqlite3_errmsg()
获取错误描述 任何操作失败时 字符串
2:预编译使用:
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <sqlite3.h>
// 用来显示数据的回调函数
int showData(void*,int,char**,char**);
bool login(char* name, char* password){
char sql[] = "select * from student where name = ? and password = ? ";
// 1: 打开数据库
sqlite3 *db;
//链接数据库
int rc = sqlite3_open("school.db",&db);
if(rc != 0){
perror("数据库,打开失败!");
exit(EXIT_FAILURE);
}
char *errmsg = 0;
// 2: -------------------------------------------------
// 2-1:执行sql的预编译:
sqlite3_stmt* stmt;
sqlite3_prepare_v2(
db, /* Database handle */
sql, /* SQL statement, UTF-8 encoded */
-1, /* Maximum length of zSql in bytes. */
&stmt, /* OUT: Statement handle */
NULL /* OUT: Pointer to unused portion of zSql */
);
// 2-2: 对问号进行赋值:
// int sqlite3_bind_int(sqlite3_stmt*, int, int); // 绑定int值,使用的函数
// 把name绑定到1号位置的问号
sqlite3_bind_text( stmt ,1 , name , -1 , SQLITE_STATIC );
sqlite3_bind_text( stmt ,2 , password , -1 , SQLITE_STATIC );
// 2-3: 执行:
bool f = false;
if(sqlite3_step(stmt)== SQLITE_ROW){
// 2-4: 得到查询的结果
int id = sqlite3_column_int(stmt, 0);
char name[32] = {0};
strcpy(name, sqlite3_column_text(stmt, 1));
char password[32] = {0};
strcpy(password, sqlite3_column_text(stmt, 2));
printf("id是:%d, 用户名是:%s , 密码:%s \n", id, name, password);
f = true;
}
// 3: 关闭数据库
sqlite3_close(db);
return f;
}
void showAll(){
char sql[] = "select * from student ";
// 1: 打开数据库
sqlite3 *db;
//链接数据库
int rc = sqlite3_open("school.db",&db);
if(rc != 0){
perror("数据库,打开失败!");
exit(EXIT_FAILURE);
}
char *errmsg = 0;
// 2: -------------------------------------------------
// 2-1:执行sql的预编译:
sqlite3_stmt* stmt;
sqlite3_prepare_v2(
db, /* Database handle */
sql, /* SQL statement, UTF-8 encoded */
-1, /* Maximum length of zSql in bytes. */
&stmt, /* OUT: Statement handle */
NULL /* OUT: Pointer to unused portion of zSql */
);
// 2-3: 执行:
while(sqlite3_step(stmt)== SQLITE_ROW){
// 2-4: 得到查询的结果
int id = sqlite3_column_int(stmt, 0);
char name[32] = {0};
strcpy(name, sqlite3_column_text(stmt, 1));
char password[32] = {0};
strcpy(password, sqlite3_column_text(stmt, 2));
int age = sqlite3_column_int(stmt, 3);
char gender[32] = {0};
strcpy(gender, sqlite3_column_text(stmt, 4));
printf("id是:%d, 用户名是:%s , 密码:%s , 年龄:%d , 性别:%s\n", id, name, password, age, gender);
}
// 要释放stmt内存
sqlite3_finalize(stmt);
// 3: 关闭数据库
sqlite3_close(db);
}
int main(int argc, char *argv[]) {
/*
char name[] = "jack";
char password[] = "loverose";
bool f = login(name, password);
if(f){
puts("登录成功");
}else {
puts("用户名或密码错误!");
}
*/
showAll();
return 0;
}
为什么在查询数据时使用 SQLITE_ROW
而不是 SQLITE_DONE
?
1. 核心区别
返回值 | 含义 | 适用场景 |
---|---|---|
SQLITE_ROW |
当前有数据行可读取 | 仅用于 SELECT 查询,表示结果集中有数据 |
SQLITE_DONE |
语句执行完成 | 用于所有SQL语句,表示操作结束(无更多数据或操作完成) |
2. 代码场景分析
您的代码是典型的 SELECT查询数据读取逻辑:
if(sqlite3_step(stmt) == SQLITE_ROW) { // ✅ 正确用法
int id = sqlite3_column_int(stmt, 0);
char name[32] = {0};
strcpy(name, sqlite3_column_text(stmt, 1));
// ...读取其他列...
}
为什么不能用 SQLITE_DONE
?
逻辑矛盾:
SQLITE_DONE
表示"没有数据可读"或"操作完成"- 如果用它作为条件,代码块永远不会执行(因为当返回
SQLITE_DONE
时,已经无法读取数据)
执行流程:
sqlite3_step(stmt)
首次调用时:- 如果有数据 → 返回
SQLITE_ROW
- 如果无数据 → 直接返回
SQLITE_DONE
- 如果有数据 → 返回
- 用
== SQLITE_DONE
判断会直接跳过数据读取
3. 正确模式对比
SELECT查询(必须用 SQLITE_ROW
)
// 正确写法:循环读取每一行
while (sqlite3_step(stmt) == SQLITE_ROW) {
// 读取当前行数据
int id = sqlite3_column_int(stmt, 0);
// ...
}
// 退出循环时,说明已返回 SQLITE_DONE(数据读完)
INSERT/UPDATE/DELETE(用 SQLITE_DONE
)
// 正确写法:判断操作是否完成
if (sqlite3_step(insert_stmt) == SQLITE_DONE) {
printf("插入成功,影响行数:%d\n", sqlite3_changes(db));
}
4. 底层原理
SQLITE_ROW
机制:- SQLite 内部维护一个结果集游标
- 每次
sqlite3_step()
推进游标到下一行 - 返回
SQLITE_ROW
时,游标指向有效数据行
SQLITE_DONE
触发时机:- SELECT:游标越过最后一行
- INSERT/UPDATE/DELETE:语句执行完毕
5. 错误用法示例
// ❌ 错误写法:永远读不到数据
if (sqlite3_step(stmt) == SQLITE_DONE) {
// 这里不会执行,因为:
// 1. 如果有数据,先返回 SQLITE_ROW
// 2. 如果无数据,条件成立但已无数据可读
int id = sqlite3_column_int(stmt, 0); // 无效操作!
}
6. 完整查询流程
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, "SELECT id, name FROM users", -1, &stmt, NULL);
// 方法1:单行查询(非循环)
if (sqlite3_step(stmt) == SQLITE_ROW) {
printf("第一行数据:id=%d\n", sqlite3_column_int(stmt, 0));
}
// 方法2:多行查询(循环)
while (sqlite3_step(stmt) == SQLITE_ROW) {
printf("id=%d, name=%s\n",
sqlite3_column_int(stmt, 0),
sqlite3_column_text(stmt, 1));
}
sqlite3_finalize(stmt);
7. 总结
SQLITE_ROW
- SELECT查询的"数据就绪"信号
- 唯一用于读取数据的判断条件
SQLITE_DONE
- 所有SQL语句的"终止"信号
- 用于确认操作完成,不能用于读取数据
关键记忆点:
要读数据,等
ROW
;
操作结束,看DONE
。
SQLite 结果集指针机制详解
1. 指针(游标)的工作方式
当执行 SELECT
查询时,SQLite 内部会创建一个结果集游标,其行为如下:
游标状态 | 返回值 | 数据访问能力 |
---|---|---|
指向有效行 | SQLITE_ROW |
可通过 sqlite3_column_* 读取当前行 |
越过最后一行 | SQLITE_DONE |
无法再读取数据 |
2. 数据检索流程
3. 关键区别
场景 | SQLITE_ROW |
SQLITE_DONE |
---|---|---|
游标位置 | 停在有效数据行 | 已越过结果集末尾 |
数据可读性 | 可立即读取当前行 | 无法读取任何数据 |
典型代码位置 | while(sqlite3_step()==SQLITE_ROW) |
循环结束后判断 |
4. 技术细节
游标初始化:
- 执行
sqlite3_prepare_v2()
后,游标位于结果集起始位置之前 - 首次
sqlite3_step()
将游标移动到第一行(如果有数据)
- 执行
数据访问时机:
// 游标状态变化示例: // 初始状态:游标在结果集起始位置之前 sqlite3_step(stmt); // 游标移动到第1行,返回SQLITE_ROW sqlite3_column_int(stmt,0); // 读取第1行数据 sqlite3_step(stmt); // 游标移动到第2行(或返回SQLITE_DONE)
边界情况:
- 空结果集:首次
sqlite3_step()
直接返回SQLITE_DONE
- 最后一行:最后一次返回
SQLITE_ROW
后,下次调用返回SQLITE_DONE
- 空结果集:首次
5. 正确用法模板
// 准备语句
sqlite3_prepare_v2(db, "SELECT id,name FROM table", -1, &stmt, NULL);
// 遍历结果集
while(sqlite3_step(stmt) == SQLITE_ROW) {
// 游标有效时读取数据
int id = sqlite3_column_int(stmt, 0);
const char* name = sqlite3_column_text(stmt, 1);
}
// 检查是否正常结束
if(sqlite3_errcode(db) == SQLITE_DONE) {
printf("成功遍历所有数据\n");
}
**6. 常见误区解释
误区:“
SQLITE_DONE
也可以用来读数据”- 事实:当返回
SQLITE_DONE
时,游标已失效,任何sqlite3_column_*
调用都将返回无意义数据
- 事实:当返回
误区:“每次调用
sqlite3_step()
都会执行一次查询”- 事实:查询只执行一次,
sqlite3_step()
只是移动游标位置
- 事实:查询只执行一次,
7. 性能优化提示
在获取文本数据时,优先使用:
const unsigned char* text = sqlite3_column_text(stmt, col); // 而不是拷贝操作(如strcpy)
对于大量数据处理:
sqlite3_exec(db, "BEGIN TRANSACTION", 0, 0, 0); while(sqlite3_step(stmt) == SQLITE_ROW) { // 批量处理 } sqlite3_exec(db, "COMMIT", 0, 0, 0);
关于 sqlite3_step()
和 sqlite3_reset()
的正确使用场景
1. 核心区别
函数 | 作用 | 使用场景 | 是否改变语句状态 |
---|---|---|---|
sqlite3_step() |
推进语句执行/移动结果集游标 | 每次需要读取新数据或执行操作时调用 | 会改变内部状态 |
sqlite3_reset() |
将语句重置到初始状态 | 需要重复使用预处理语句时调用 | 恢复初始状态 |
2. 为什么示例中不需要 sqlite3_reset()
?
在之前的SELECT查询示例中:
while (sqlite3_step(stmt) == SQLITE_ROW) {
// 读取数据...
}
不需要 reset
的原因是:
单次查询生命周期:
prepare
→step
循环 →finalize
- 语句对象即将被销毁(
finalize
),无需重置
游标自动管理:
- 当
sqlite3_step()
返回SQLITE_DONE
时,游标已自动失效 - 如果不再使用该语句,直接
finalize
更高效
- 当
3. 必须使用 sqlite3_reset()
的场景
场景1:重复执行同一条语句
// 准备可复用的语句
sqlite3_prepare_v2(db, "INSERT INTO logs(msg) VALUES(?)", -1, &stmt, NULL);
// 第一次执行
sqlite3_bind_text(stmt, 1, "Error1", -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_reset(stmt); // ⚠️ 必须重置!否则下次绑定会失败
// 第二次执行
sqlite3_bind_text(stmt, 1, "Error2", -1, SQLITE_STATIC);
sqlite3_step(stmt);
sqlite3_finalize(stmt);
场景2:批量参数化查询
sqlite3_prepare_v2(db, "UPDATE users SET score=? WHERE id=?", -1, &stmt, NULL);
for (int i = 0; i < 100; i++) {
sqlite3_bind_int(stmt, 1, scores[i]);
sqlite3_bind_int(stmt, 2, ids[i]);
sqlite3_step(stmt);
sqlite3_reset(stmt); // ⚠️ 必须重置才能绑定新参数
}
4. 不需要 reset
的例外情况
场景 | 处理方式 |
---|---|
语句只用一次 | 直接 finalize |
连续执行非参数化语句 | 直接多次 step (如执行多个独立SQL) |
5. 底层原理图解
6. 错误用法示例
// ❌ 错误:未reset导致绑定失败
sqlite3_prepare_v2(db, "INSERT INTO t VALUES(?)", -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, 100);
sqlite3_step(stmt);
// 直接再次绑定会失败!
sqlite3_bind_int(stmt, 1, 200); // 错误:SQLITE_MISUSE
sqlite3_step(stmt);
修复方法:
// ✅ 正确:重置后复用
sqlite3_reset(stmt); // 重置语句状态
sqlite3_clear_bindings(stmt); // 可选:清除旧绑定
sqlite3_bind_int(stmt, 1, 200);
sqlite3_step(stmt);
7. 最佳实践总结
查询数据(SELECT):
- 单次查询:无需
reset
,直接finalize
- 复用查询:
reset
+ 重新绑定参数
- 单次查询:无需
修改数据(INSERT/UPDATE/DELETE):
- 批量操作:每次执行后必须
reset
- 单次操作:直接
finalize
- 批量操作:每次执行后必须
事务处理:
sqlite3_exec(db, "BEGIN", 0, 0, 0); for (...) { sqlite3_reset(stmt); // 绑定参数... sqlite3_step(stmt); } sqlite3_exec(db, "COMMIT", 0, 0, 0);
8. 性能影响
- 不必要地调用
reset
:增加约 5-10% 的性能开销 - 漏掉必要的
reset
:导致内存泄漏或绑定失败
最终结论
- 需要
reset
:当你想复用预处理语句时(特别是带参数的语句) - 不需要
reset
:当语句即将被销毁(finalize
)或只执行一次时
记忆口诀:
一用到底就销毁(
finalize
),
想再复用需重置(reset
)!
1: 分别封装四个函数:完成增删改查操作<要使用预编译完成>:
- 增加:根据User结构体,增加一条数据
- 修改:根据User结构体,修改一条数据
- 删除:根据id删除一条数据
- 查询:
- 根据id查询相应的数据,得到一个User结构体数据
- 查询所有数据,得到一个User结构体数组