永久存储程序数据有两种方式:
- 用文件存储
- 用数据库存储
对于多条记录的存储而言,采用文件时,插入、删除、查找的效率都会很差,为了提高这些操作的效率,有计算机科学家设计出了数据库存储方式
一、数据库
数据库的基本组成:
用来管理数据库的软件被称数据库管理系统
一个数据库管理系统管理着多个数据库
一个数据库中可以包含很多张表
一张表中拥有很多记录(行)
一条记录拥有很多字段(列)
每条记录的每个字段存放不同类型的数据
因此,数据库本质上是表的集合
数据库按数据的组织形式分为:
- 关系型数据库 ----- 本章内容
- 非关系型数据库
数据库管理系统软件按是否支持远程分为:
- 网络版 ---- MySql Oracle SQlServer
- 单机版 ---- sqlite
网络版的数据管理系统的整体框架:
数据库管理系统发展初期,各家开发此类软件的公司采用的通信协议是不同的,这给应用客户端开发造成很大的麻烦
因此,经过一段时间的发展,全球几家著名的数据库管理系统开发公司制定了统一的通信协议,这个统一的通信协议被称为SQL(Structure Query Language)
随着关系型数据库的不断发展,SQL成为所有数据库管理系统的统一协议
每个数据库管理系统都会提供一个命令行界面的简易客户端,应用程序员可以使用这个简易客户端学习、练习SQL语句以及辅助开发
每一条SQL语句就是一个操作请求
二、SQLite
一个单机版的数据库管理系统
所有操作请求也遵循SQL标准
开源、精悍
SQLite安装:
1.安装SQLite3
命令行下输入:sudo apt-get install sqlite3
2.安装SQLite3编译需要的工具包
命令行下输入: sudo apt-get install libsqlite3-dev
如出现问题尝试:
sudo dpkg --purge --force-depends libsqlite3-0
sudo apt-get install libsqlite3-0
sudo apt-get install -f
sudo apt-get install libsqlite3-dev
sudo apt-get install sqlite3
支持三种字段数据类型:
INTEGER 或 INT 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。
REAL 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。
TEXT 值是一个文本字符串,单引号或双引号括起的字符串
使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储
三、sqlite3简易客户端的使用
1. 运行
Linux>sqlite3 ------ 只是进入sqlite3的命令行界面,不创建打开任何数据库
Linux>sqlite3 路径/数据库文件名 ----- 先创建打开指定的数据库,然后进入sqlite3的命令行界面
sqlite3管理的一个数据库,在Linux系统下用一个文件表示它,习惯上将这个文件命名为 xxx.db
2. 两种命令
- . 开头的命令:简易客户端的内部命令
- 以 ; 结尾的命令:; 前的内容为SQL语句,; 是简易客户端要求的结尾符
3. 常用内部命令
创建打开一个指定的数据库
sqlite> .open 路径/xxx.db
sqlite> .help
查看正在使用的数据库
sqlite> .databases
SQL语句操作结果按列对齐 ----- 重要
sqlite> .mode column
显示某表时是否显示表头 ----- 重要
sqlite> .headers on/off
显示数据中的表 ------ 重要
sqlite> .tables
退出sqlite命令行 ---- 重要
sqlite> .quit
四、常用SQL语句
SQL语句组成:关键字 + 运算符 + 字段名或表名 + 常量
SQL语句的关键字可以全大写、也全小写、甚至可以大小混合,一般建议采用全大写
字段名或表名的大小是区分的,aaa、Aaa、AAA认为是不同的字段名或表名,自建表建议字段名或表名全小写
1. 建表
CREATE TABLE [IF NOT EXISTS] table_name(
column_name1 datatype[(size)] [PRIMARY KEY] [AUTOINCREMENT] [NOT NULL],
column_name2 datatype,
column_name3 datatype,
.....
column_nameN datatype,
);
[]:表示可选,选用时不带[]
[AUTOINCREMENT]:只能用于INTEGER 表示如果插入新记录时没有指定该字段的值,则其值为前一条记录同字段值+1
[PRIMARY KEY]:主键,唯一标识表中的记录 主键:表中每条记录的该字段值不会重复,也即该字段值可以唯一标识一条记录
[IF NOT EXISTS]:如果表不存在则建表,如果存在则什么都不做
[NOT NULL]:表示字段内容不能为空
示例:
sqlite> CREATE TABLE student(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
...> name TEXT,sex TEXT(1),score REAL);
2. 删表
DROP TABLE 表名;
示例:
sqlite> CREATE TABLE ttt(xxx TEXT,yyy TEXT);
sqlite> DROP TABLE ttt;
3. 插入新记录
通用:
INSERT INTO table_name(column1, column2,...columnN) VALUES(value1, value2,...valueN);
不缺字段时:
INSERT INTO table_name VALUES(value1,value2,...valueN);
4. 查询记录
SELECT column1, column2, columnN FROM table_name [WHERE condition];
SELECT * FROM table_name [WHERE condition];
*表示查询结果显示所有字段
无WHERE子句则查询所有记录
5. where子句
所谓子句就是不能成为独立的SQL语句,只能成为一条SQL语句的一部分
WHERE子句用来为一些操作指定条件
WHERE condition
condition可以是单个条件或由AND、OR连接的多个条件
比较运算符支持:
= != > < >= <= 用法:字段名 运算符 值
IN()用法:字段名 IN(值1,值2,...,值n) 表示字段值为值1或值2或....
NOT IN()用法:字段名 NOT IN(值1,值2,...,值n) 表示字段值不为值1也不为值2也不为....
LIKE用法:字段名 LIKE 模式字串 模式字串中可以用%号表示任意多个字符,用_表示单个字符,默认不区分大小写
GLOB用法:字段名 GLOB 模式字串 模式字串中可以用*号表示任意多个字符,用?表示单个字符,区分大小写
IS NOT NULL用法:字段名 IS NOT NULL 判断字段值是否为空
IS NULL用法:字段名 IS NULL 判断字段值是否为空
BETWEEN AND用法:字段名 BETWEEN 值1 AND 值2 字段值>=值1 且 字段值<=值2
NOT反条件用法:字段名 NOT 其它条件
6. 修改记录
UPDATE table_name SET column1 = value1, column2 = value2...., columnN = valueN WHERE [condition];
无WHERE子句则对所有记录进行修改
7. 删除记录
DELETE FROM table_name WHERE [condition];
无WHERE子句则删除所有记录
8. Sort子句
ORDER BY 字段名 ASC 或 DESC
ASC升序
DESC降序
9. SQL语句中的函数
datetime('now','localtime') ---- 产生当前日期时间的字符串
使用示例:
INSERT INTO Table_name VALUES(datetime('now','localtime'));
count(*) 对查询结果求记录数
使用示例:
SELECT count(*) FROM student;
五、sqlite3函数库
SQL按执行完毕后是否有记录结果分为两种:
- 一种SQL语句的执行只论是否成功。例如:建表、删表、插入记录、修改记录、删除记录
- 另一种SQL语句执行完毕产生多条记录结果。例如:查询
sqlite3_open参照图:
sqlite3_exec及其回调函数的参考图:
sqlite3_get_table函数参考图1:
//头文件包含
#include <sqlite3.h>
/*链接选项
-lsqlite3
*/
//1、打开/创建一个数据库
int sqlite3_open(const char *filename, sqlite3 **ppDb );
/*
功能:打开指定的数据库,如果数据库并存在则创建并打开指定的数据库,并创建一个后续操作该数据库的引擎
参数:
filename:带路径的数据库文件名
ppdb:指向sqlite句柄的指针,调用前定义一个sqlite3 *指针变量,取该指针变量的地址
返回:成功 SQLITE_OK (值为0),否则返回其他值。
*/
//2、如果打开数据库失败,open函数只是返回错误码,如果想要错误原因的字符串描述则调用该函数
const char *sqlite3_errmsg(sqlite3 *db);
/*
返回值:返回错误信息
*/
//3、回调函数执行sql语句
//更加适用于sql语句执行没有记录结果的情况(此时第3、4参数填NULL)或对需要依次对每条操作结果记录进行处理的情况
int sqlite3_exec(sqlite3* pDB, const char *sql, sqlite_callback callback, void*para, char** errMsg);
/*
功能:执行SQL语句,查询的结果返回给回调函数callback,多个结果的操作,每个结果都会调用一次callback
参数:
pDB:数据库引擎的句柄。
sql:待执行的SQL 语句字符串,以’\0’结尾,不以';'结尾。
callback:回调函数,用来处理查询结果,如果不需要回调(比如做insert 或者delete 操作时),可以置NULL。
para:要传入回调函数的指针参数,没有可以置为NULL。
errMsg:一级指向空间存放着一个char *类型的元素,该元素指向空间(二级指向空间)存放着一个字符串,该字符串内容描述出错原因,
因此:1. 调用前定义一个char *类型的指针变量,传该指针变量的地址给该形参。
2. 由于其二级指向空间是动态分配的,因此使用完后需要调用sqlite3_free函数进行释放;
返回值:执行成功返回SQLITE_OK,否则返回其他值
*/
//4、回调函数
typedef int (*sqlite_callback)(void* para, int columnCount, char** columnValue,char** columnName);
/*
功能:由用户处理查询的结果,每找到一条记录自动执行一次回调函数
参数:
para:从sqlite3_exec()传入的参数指针;
columnCount:查询到的这一条记录有多少个字段(即这条记录有多少列);
columnValue:查询出来的数据都保存在这里,它实际上是个1 维数组(不要以为是2 维数组),每一个元素都是一个char * 值,是一个字段内容(用字符串来表示,以‘\0’结尾)
columnName:与columnValue 是对应的,表示这个字段的字段名称。
返回值:执行成功返回SQLITE_OK,否则返回其他值
*/
//5、关闭
int sqlite3_close(sqlite3 *ppDb);
/*
功能:关闭sqlite数据库。
参数:ppdb:数据库句柄。
返回值:成功返回0,失败返回错误码
*/
//6、释放
void sqlite3_free(void * errMsg );
/*
功能:释放存放错误信息的内存空间
参数:errMsg: 返回错误信息
*/
//7、非回调来执行sql语句
//更加适用于SQL语句执行后有一条或多条记录结果,且程序期望对所有记录结果的整体进行处理的情况
int sqlite3_get_table(
sqlite3 *db, /* An open database */
const char *zSql, /* SQL to be evaluated */
char ***pazResult, /* Results of the query */
int *pnRow, /* Number of result rows written here */
int *pnColumn, /* Number of result columns written here */
char **pzErrmsg /* Error msg written here */
);
/*
功能:非回调来执行sql语句
参数:
db:数据库句柄
zSql:要执行的SQL语句
pazResult:查询结果,一维数组,定义一个char **的二级指针变量,调用函数时传该二级指针变量的地址,不使用应立即调用
sqlite3_free_table(result)进行释放
pnRow:查询出多少条记录(查出多少行) 不包括字段名所在行
pnColumn:多少个字段(查出多少列)
pzErrmsg:错误信息
返回值:执行成功返回SQLITE_OK,否则返回其他值
注:pazResult的字段值是连续的,是一维数组不是二维数组,从第0索引到第 pnColumn- 1索引都是字段名称,
从第 pnColumn索引开始,后面都是字段值。
*/
//8、释放pazResult查询结果
void sqlite3_free_table(char **pazResult);
/*
参数:pazResult:指向空间存放着查询结果,指向空间为元素类型是char *的一维数组
*/
示例代码:
#include <stdio.h>
#include <sqlite3.h>
int handle_a_record(void *para,int cols,char **ppval,char **ppname);
int main(){
sqlite3 *pdb = NULL;
int ret = 0;
ret = sqlite3_open("./test.db",&pdb); // open
if(ret != SQLITE_OK){
printf("sqlite3 not open,because %s\n",sqlite3_errmsg(pdb));
sqlite3_close(pdb);
pdb = NULL;
return 1;
}
{
char sql[80] = "";
char *perr = NULL;
sprintf(sql,"SELECT * FROM stu"); // write instruction
ret = sqlite3_exec(pdb,sql,handle_a_record,NULL,&perr); // exec()
if(ret != SQLITE_OK){ // error
printf("exec %s failed,because %s\n",sql,perr);
sqlite3_free(perr); // sqlite3_free()
perr = NULL;
}
}
{
char sql[80] = "";
char *perr = NULL;
char **ppret = NULL;
int rows = 0;int cols = 0;int i = 0;int j = 0;
sprintf(sql,"SELECT * FROM stu"); // write instruction
ret = sqlite3_get_table(pdb,sql,&ppret,&rows,&cols,&perr); // exec()
if(ret != SQLITE_OK){ // error
printf("exec %s failed,because %s\n",sql,perr);
sqlite3_free(perr); // sqlite3_free()
perr = NULL;
}
else{
for(j = 0;j < cols;j++){
printf("%-20s ",*(ppret + j));
}
printf("\n");
}
for(i = 1;i <= rows;i++){
for(j = 0;j < cols;j++) printf("%-20s ",*(ppret + i * cols + j));
printf("\n");
}
sqlite3_free_table(ppret);
ppret = NULL;
}
sqlite3_close(pdb); // sqlite3_close()
pdb = NULL;
return 0;
}
int handle_a_record(void *para,int cols,char **ppval,char **ppname){
int i = 0;
for(i = 0;i < cols;i++){
printf("%s | ",*(ppval + i));
}
printf("\n");
return SQLITE_OK;
}
输出:
六、sqlite3函数库的并发模式
- 单线程模式 ----- 无锁模式
- 串行模式 ------ 默认模式,非阻塞互斥锁实现
- 读者写者模式 ------ 读写锁实现
配置sqlite3函数库的并发模式可以通过:1. 重新编译sqlite3函数库的源码 2.调用相关的接口函数进行配置
实际项目中大部分情况采用其默认模式
串行模式时,需要对sqlite3_exec、sqlite3_get_table函数进行如下形式的二次封装:
int my_sqlite3_exec(sqlite3* pdb, const char *sql, sqlite_callback callback, void*para, char** errmsg){
int ret = 0;
do {
ret = sqlite3_exec( pdb , sql, callback , para , errmsg );
if (ret == SQLITE_BUSY || ret == SQLITE_LOCKED){
usleep(30 * 1000);
continue;
}
else{
break;
}
} while(1);
return ret;
}
int my_sqlite3_get_table(
sqlite3 *db, /* An open database */
const char *zSql, /* SQL to be evaluated */
char ***pazResult, /* Results of the query */
int *pnRow, /* Number of result rows written here */
int *pnColumn, /* Number of result columns written here */
char **pzErrmsg /* Error msg written here */
){
int ret = 0;
do {
ret = sqlite3_get_table(db,zSql,pazResult,pnRow,pnColumn,pzErrmsg);
if (ret == SQLITE_BUSY || ret == SQLITE_LOCKED){
usleep(30 * 1000);
continue;
}
else{
break;
}
} while(1);
return ret;
}
七、二次封装
实际项目代码中,直接调用sqlite3函数库中的函数实现相关功能,会很不方便,往往会将项目中可能用到的所有数据库操作代码单独作为一个模块,该模块又按表分为几个子模块(一般命名为xxxhelper),由这个模块及其子模块向其它项目代码提供需要的数据库操作接口。
public子模块:不隶属于任何一个helper子模块的操作函数,归于本子模块
一般项目中建议采用如下方案:
/*一、public子模块 -------- 项目名_db_public.c*/
/*1. 函数功能:创建打开数据库,然后带IF NOT EXISTS关键字去建表*/
sqlite3 *create_db_engine(const char *pdbfile)
{
1. 调用sqlite3_open
2. 建表1
3. 建表2
。。。。
n+1. 建表n
成功返回引擎地址,失败返回NULL
}
/*2. 对sqllite3_exec的二次封装 ------ 见上一节*/
/*3. 对sqllite3_get_table的二次封装 ------ 见上一节*/
/*4. destroy_db_engine*/
void destroy_db_engine(sqlite3 *pdb)
{
sqlite3_close(pdb);
}
/*二、对表1的各种操作函数封装 表1名helper.c*/
int insert_new_record_into_表名(sqlite3 *pdb,.........)
{
组织sql语句
调用my_sqlite3_exec 或 my_sqlite3_get_table函数执行sql语句
。。。。。。
}
......
???? xxxxx(sqlite3 *pdb,.......)
{
通过多次执行sql语句组合完成本函数功能
}
/*三、对表2的各种操作函数封装 表2名helper.c*/
。。。。。。。。。。。。。。。