maraidb和sqlite3部分命令操作区别记录
1.安装sqlite3 在实现我的视频点播系统项目时,我尝试封装了两种数据库的调用逻辑
这里封装sqlite3的原因是,sqlite3主要针对的就是嵌入式
数据库,其性能可能不如mysql,但是就好在可以带着走
。安装也很方便,内存占用相对于mariadb来说也降低了很多。
教程:安装maraidb
在上面的博客中,安装maraidb需要很多步骤,还需要修改配置文件 中的默认字符集为UTF8。
而安装sqlite3就要多方便有方便了,而且sqlite3默认采用的就是uft8字符集,完全不需要修改!
1 2 3 4 sudo yum install sqlite-devel sudo apt install sqlite3 libsqlite3-dev
就这一行命令就搞定安装了!安装完成后,可以用如下命令看看sqlite3的版本。
如果没有安装,执行这个命令会告知 command not found
;
1 2 [root@1c2261732150:~]# sqlite3 --version 3.26.0 2018-12-01 12:34:55 bf8c1b2b7a5960c282e543b9c293686dccff272512d08865f4600fb58238alt1
安装新版本 sqlite3 默认安装的版本可以看到已经是 2018 年的老东西了,可以尝试用下面的办法安装最新版本,需要修改以下命令中的链接和文件名,最新版本的 sqlite 去官网上找 https://www.sqlite.org/download.html
1 2 3 4 5 6 7 sudo yum remove sqlite-devel wget https://www.sqlite.org/2019/sqlite-autoconf-3280000.tar.gz tar -xzf sqlite-autoconf-3280000.tar.gz cd sqlite-autoconf-3280000./configure make sudo make install
选择官网上名字为 sqlite-autoconf 的 tar.gz 下载就行了。请注意安装之前一定要卸载原有的 sqlite。
请注意,如果你需要用 python 操作 sqlite,在更新 sqlite 版本后需要重新编译安装 python。
依照如上命令,我成功更新 sqlite 到 2023 年的最新版本了
1 2 ## sqlite3 --version 3.44.0 2023-11-01 11:23:50 17129ba1ff7f0daf37100ee82d507aef7827cf38de1866e2633096ae6ad81301 (64-bit)
更新参考博客 https://number1.co.za/upgrading-sqlite-on-centos-to-3-8-3-or-later/
2.基本操作的区别 如下列出一些基本操作在mariadb命令行中,和在sqlite3命令行中的区别
为了方便,注释中m指代mysql,s指代sqlite3
1 2 3 4 5 6 7 8 9 10 11 show databases; .database use db_name; .open db_name.db; show tables; .tables quit .quit
这便是基础操作的一些区别,更深入的操作我暂时还没有学到。
其中进入数据库的操作就能看出来sqlite的特性,只要有这个.db
文件,你就可以很轻松的在另外一个主机上恢复之前的数据(或者进行备份),这也是带着走
的体现。
3.创建表 mysql和sqlite3所支持的数据类型也有区别
sqlite3数据类型 描述 NULL 值是一个 NULL 值。 INTEGER 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。 REAL 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。 TEXT 值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。 BLOB 值是一个 blob 数据,完全根据它的输入存储。
在sqlite3中,没有varchar类型。但我们依旧可以使用TEST(8)
来限制字符串类型的长度
以我的视频点播项目所用数据库为例,以下是mariadb创建数据表的sql语句
1 2 3 4 5 6 7 8 create table tb_video( id VARCHAR (8 ) NOT NULL DEFAULT (substring (UUID(), 1 , 8 )) comment '视频id' , name VARCHAR (50 ) comment '视频标题' , info text comment '视频简介' , video VARCHAR (255 ) comment '视频链接' , cover VARCHAR (255 ) comment '视频封面链接' , insert_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '视频创建时间' );
在这里我采用了mariadb自带的uuid函数来生成uuid字符串,并使用substring
函数截取了uuid前8位的内容,作为视频的唯一id
而如果想让mariadb来保证id字段唯一,可以使用如下命令进行约束
1 2 3 4 5 6 7 8 9 create table tb_video( id VARCHAR (8 ) NOT NULL DEFAULT (substring (UUID(), 1 , 8 )) comment '视频id' , name VARCHAR (50 ) comment '视频标题' , info text comment '视频简介' , video VARCHAR (255 ) comment '视频链接' , cover VARCHAR (255 ) comment '视频封面链接' , insert_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP comment '视频创建时间' , UNIQUE (id) );
以下是sqlite3的操作,sqlite3中并不支持comment对字段进行注释。
这里要想让id字段唯一,直接在字段类型后面跟着UNIQUE
就可以了
1 2 3 4 5 6 7 8 9 10 CREATE TABLE IF NOT EXISTS tb_video( id TEXT(8 ) UNIQUE NOT NULL DEFAULT (lower ((hex(randomblob(4 ))))), name TEXT NOT NULL , info TEXT, video TEXT NOT NULL , cover TEXT NOT NULL , insert_time TIMESTAMP DEFAULT (datetime('now' , '+8 hours' )) );
查看表结构 在 msyql 中可以用 desc 快捷查看表结构,但是 sqlite3 中不支持此功能。
在 sqlite3 数据库中用如下语句查看数据库中的建表语句,依此可以看到表结构。
1 SELECT sql FROM sqlite_master WHERE type="table";
4.插入删除数据 在我项目所用字段中,二者插入/删除数据的操作完全相同,这里就不记录了
1 2 3 4 5 6 7 8 9 drop table tb_video;insert into tb_video (name, info, video, cover) values ('名字1' ,'说明信息1' ,'test1' ,'testc1' );select * from tb_video;select * from tb_video where id= '45f78a68' ; delete from tb_video where id = 'D81382A8' ;
5.使用cpp操作的时候 mysql必须要进行init,此时就需要指定目标数据库了 而sqlite3并不需要进行数据库的连接操作,我们就可以实现在cpp中进行数据库的创建、数据表的创建等操作。 c语言操作sqlite3的方法,可以查看菜鸟教程 。上面的用例很详细(虽然没有写注释,但还是能看懂的)我就不写博客了~
5.1 查询时的回调 这里只对select命令操作进行说明,在sqlite3中,所有命令都是用下面这个函数来执行的
1 2 3 4 5 6 7 SQLITE_API int sqlite3_exec ( sqlite3*, const char *sql, int (*callback)(void *,int ,char **,char **), void *, char **errmsg ) ;
如果你执行的是插入、更新等等sql语句,sqlite_callback
函数不会被调用(我测试过了)。目前我只发现select语句会调用这个callback函数。
比如我的数据库tb_video
中有如下两行数据
1 2 df65c8c5|名字1|说明信息1|test1|testc1|2023-05-04 16:57:10 80cd3f51|名字1|说明信息1|test1|testc1|2023-05-04 19:02:05
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <stdio.h> #include <stdlib.h> #include <sqlite3.h> static int callback (void *NotUsed, int argc, char **argv, char **azColName) { int i; for (i = 0 ; i < argc; i++) { printf ("%s = %s\n" , azColName[i], argv[i] ? argv[i] : "NULL" ); } printf ("\n" ); return 0 ; } void SqliteTest () { sqlite3 *db; char *zErrMsg = 0 ; std ::string sql; int ret; if (sqlite3_open("test.db" , &db)) { fprintf (stderr , "Can't open database: %s\n" , sqlite3_errmsg(db)); exit (0 ); } else { fprintf (stderr , "Opened database successfully\n" ); } sql = "select * from tb_video;" ; ret = sqlite3_exec(db, sql.c_str(), callback, 0 , &zErrMsg); if (ret != SQLITE_OK) { fprintf (stderr , "SQL error: %s\n" , zErrMsg); sqlite3_free(zErrMsg); } else { fprintf (stdout , "SQL successfully\n" ); } sqlite3_close(db); }
用这个函数来查询,会打印如下的结果。从这个结果中,就能推测出callback函数4个参数分别的作用,已经在代码的注释中说明了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Opened database successfully id = df65c8c5 name = 名字1 info = 说明信息1 video = test1 cover = testc1 insert_time = 2023-05-04 16:57:10 id = 80cd3f51 name = 名字1 info = 说明信息1 video = test1 cover = testc1 insert_time = 2023-05-04 19:02:05
其中,第四个参数是给callback函数传入的第一个入参。
5.2 通过回调插入数据到Json字符串 我的视频点播项目在查询的时候,需要将结果保存为json字符串,如果使用mysql的c++操作,就可以直接在遍历结果的二维数组时,将结果放入到Json::Value
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 bool SelectAll (Json::Value *video_s) { #define SELET_ALL "select * from %s;" std::string sql; sql.resize (512 ); sprintf ((char *)sql.c_str (),SELET_ALL,_video_table.c_str ()); _mutex.lock (); if (!MysqlQuery (_mysql, sql)) { _mutex.unlock (); _log.error ("Video SelectAll" ,"query failed" ); return false ; } MYSQL_RES *res = mysql_store_result (_mysql); if (res == nullptr ) { _mutex.unlock (); _log.error ("Video SelectAll" ,"mysql store result failed" ); return false ; } int num_rows = mysql_num_rows (res); for (int i = 0 ; i < num_rows; i++) { MYSQL_ROW row = mysql_fetch_row (res); Json::Value video; video["id" ] = row[0 ]; video["name" ] = row[1 ]; video["info" ] = row[2 ]; video["video" ] = row[3 ]; video["cover" ] = row[4 ]; video["insert_time" ] = row[5 ]; video_s->append (video); } mysql_free_result (res); _mutex.unlock (); _log.info ("Video SelectAll" ,"select all finished" ); return true ; }
而在sqlite3中,就需要使用callback函数的第一个参数来进行json字符串的保存;这里因为sqlite3会给我们返回字段名字,我们就可以直接用字段明作为json的字段名,将参数作为json字段的对应参数。更省事了!
1 2 3 4 5 6 7 8 9 10 11 12 13 static int callback (void *json_videos, int argc, char **argv, char **azColName) { Json::Value* video_s = (Json::Value*)json_videos; Json::Value video; for (int i = 0 ; i < argc; i++) { video[azColName[i]] = argv[i] ? argv[i] : "NULL" ; printf ("%s = %s\n" , azColName[i], argv[i] ? argv[i] : "NULL" ); } printf ("\n" ); video_s->append (video); return 0 ; }
在主函数中,创建一个Json::Value
对象,将其强转为void*
的指针,传给callback函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Json::Value videos; sql = "select * from tb_video;" ; ret = sqlite3_exec (db, sql.c_str (), callback, (void *)&videos, &zErrMsg); if (ret != SQLITE_OK){ fprintf (stderr, "SQL error: %s\n" , zErrMsg); sqlite3_free (zErrMsg); } else { fprintf (stdout, "Table created successfully\n" ); } sqlite3_close (db);std::string json_str; vod::JsonUtil::Serialize (videos,&json_str); std::cout << json_str << std::endl;
编译执行,最终打印的json字符串如下(完整代码见 Github )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [ { "cover" : "testc1" , "id" : "df65c8c5" , "info" : "\u8bf4\u660e\u4fe1\u606f1" , "insert_time" : "2023-05-04 16:57:10" , "name" : "\u540d\u5b571" , "video" : "test1" } , { "cover" : "testc1" , "id" : "80cd3f51" , "info" : "\u8bf4\u660e\u4fe1\u606f1" , "insert_time" : "2023-05-04 19:02:05" , "name" : "\u540d\u5b571" , "video" : "test1" } ]
5.3 对表的查询 除了使用sqlite3_exec
针对数据库进行操作,还可以用下面这个函数,对指定的表进行查询
1 2 3 4 5 6 7 8 int sqlite3_get_table ( sqlite3* db, const char *zSql, char ***pazResult, int *pnRow, int *pnColumn, char **pzErrmsg ) ;
参数说明如下,这里就比较类似mysql的查询函数了,其会给我们返回结果集,以及结果的行数、列数,让我们自己遍历进行操作。
db
: 数据库连接对象,是已经打开的数据库连接。zSql
: 执行的 SQL 查询语句。pazResult
: 一个 char 类型的指针数组 (二维,每一行是一个指针,指向一个字符串),用于存储查询结果。每个元素都指向一个表示每行数据的字符串数组。最后一个元素为 NULL。pnRow
: 用于存储查询结果的行数。pnColumn
: 用于存储查询结果的列数。pzErrmsg
: 用于存储错误信息。sqlite3_get_table
函数执行查询语句时,结果集中的每个单元格都被解释为一个字符串。查询结果将被存储在指针数组 pazResult
中,每行数据占用一个字符串数组(除了最后一个元素为 NULL)。表格的第一行包含列名,后面的每行则为查询结果中的一条记录。
sqlite3_get_table
函数的作用是执行一条 SQL 查询语句,并将其结果存储在一个表格中,以便后续处理和分析。
调用完毕这个函数,处理完结果集后,需要调用如下函数释放结果集 。
1 2 3 4 5 6 char **pazResult; int nRow = 0 , nColumn = 0 ;sqlite3_get_table(db, "SELECT * FROM tb_video;" , &pazResult, &nRow, &nColumn, NULL ); sqlite3_free_table(pazResult);
5.3.1 错误示例 如下是一个示例的错误 代码!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 char **pazResult; int nRow = 0 , nColumn = 0 ,index=0 ;sqlite3_get_table (db, "SELECT * FROM tb_video;" , &pazResult, &nRow, &nColumn, NULL );std::cout << nRow << " " << nColumn << std::endl; for (int i = 0 ; i < nRow; i++){ for (int j = 0 ; j < nColumn; j++) { printf ("%-8s : %-8s\n" , pazResult[j],pazResult[i][j]); } } sqlite3_free_table (pazResult);
编译不会出错,但是执行的时候,直接会出现段错误
1 2 3 Opened database successfully 2 6 Segmentation fault
这是因为我们的pazResult
只是一个二级指针,我们并没有给他初始化为多少行多少列的模式,导致最终++的时候,会出现访问错位的情况。
如果改成下面这样的打印
1 2 3 4 cout << pazResult[j] << endl; cout << "--" << endl; cout << pazResult[i][j] << endl; cout << "---" << endl;
打印的结果就是这样的,合计是在遍历每一个字符串!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 id -- i --- name -- d --- info -- --- video -- --- cover -- --- insert_time -- 后面重复的就省略掉了
为什么不行呢?这是因为pazResult
实际上的结构是这样的
1 2 3 4 5 6 7 8 9 10 11 12 id name info video cover insert_time df65c8c5 名字1 说明信息1 test1 testc1 2023-05-04 16:57:10
使用pazResult[i][j]
进行访问:
当i是0的时候,访问的是id
字符串 此时j
就变成id
字符串里面的下标了 而nColumn
远大于id字符串的长度(id只有两个字符,而在我这里nColumn=6
) 所以就出现了段错误 Segmentation fault
! 我还是学艺不精呀!🤣
5.3.2 正确操作 正确的办法应该是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Json::Value videos; char **pazResult; int nRow = 0 , nColumn = 0 ,index=0 ;sqlite3_get_table (db, "SELECT * FROM tb_video;" , &pazResult, &nRow, &nColumn, NULL );std::cout << nRow <<" " << nColumn << std::endl; index = nColumn; for (int i = 0 ; i < nRow; i++){ for (int j = 0 ; j < nColumn; j++) { printf ("%-8s : %-8s\n" , pazResult[j],pazResult[index]); index++; } } sqlite3_free_table (pazResult);
成功打印出了数据库中两行的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Opened database successfully 2 6 id : df65c8c5 name : 名字1 info : 说明信息1 video : test1 cover : testc1 insert_time : 2023 -05 -04 16 :57 :10 id : da6d27be name : 名字2 info : 说明信息2 video : test2 cover : testc2 insert_time : 2023 -05 -04 19 :59 :38
和从数据库命令行中读取到的结果相同
1 2 3 sqlite> select * from tb_video; df65c8c5|名字1|说明信息1|test1|testc1|2023-05-04 16:57:10 da6d27be|名字2|说明信息2|test2|testc2|2023-05-04 19:59:38
最终的完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void SqliteTest () { sqlite3 *db; char *zErrMsg = 0 ; std::string sql; int ret; if (sqlite3_open ("test.db" , &db)) { fprintf (stderr, "Can't open database: %s\n" , sqlite3_errmsg (db)); exit (0 ); } else { fprintf (stderr, "Opened database successfully\n" ); } Json::Value videos; char **pazResult; int nRow = 0 , nColumn = 0 ; sqlite3_get_table (db, "SELECT * FROM tb_video;" , &pazResult, &nRow, &nColumn, NULL ); std::cout << nRow <<" " << nColumn << std::endl; int index = nColumn; for (int i = 0 ; i < nRow; i++) { Json::Value video; for (int j = 0 ; j < nColumn; j++) { video[pazResult[j]] = pazResult[index] ?pazResult[index] : "NULL" ; index++; } videos.append (video); } std::string json_str; vod::JsonUtil::Serialize (videos, &json_str); std::cout << json_str << std::endl; sqlite3_free_table (pazResult); sqlite3_close (db); }
完整输出如下,json字符串内的数据是正确的!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Opened database successfully 2 6 [ { "cover" : "testc1", "id" : "df65c8c5", "info" : "\u8bf4\u660e\u4fe1\u606f1", "insert_time" : "2023-05-04 16:57:10", "name" : "\u540d\u5b571", "video" : "test1" }, { "cover" : "testc2", "id" : "da6d27be", "info" : "\u8bf4\u660e\u4fe1\u606f2", "insert_time" : "2023-05-04 19:59:38", "name" : "\u540d\u5b572", "video" : "test2" } ]
sqlite3在返回数据的时候也会给我们返回表中的字段名。我们可以将json字段的key设置成字段名,这样就实现了字段的统一
个人认为,为了避免出现同一value而key不同的情况,最好是将应用层和数据库中的字段统一。否则出现二义性问题不好排查。特别是当一个表中的字段较多的时候。
结语 需要注意的是,二者只是适用范围的区别,并没有孰强孰弱的差距 。
以下是gpt3.5对二者区别的总结:
SQLite3 和 MySQL 都是流行的关系型数据库管理系统,但它们有不同的用途和设计重点。
SQLite3 的主要目标是作为嵌入式数据库使用,包括在移动设备和桌面应用程序中,也可以作为轻量级数据库使用。相比之下,MySQL 的主要重点是支持大型企业级应用程序和高负载服务器。 SQLite3 是一个服务器不需要客户端的完全独立的自包含数据库,MySQL 是一个客户端/服务器模型的数据库,需要一个专用的服务器端。 SQLite3 支持 SQL-92 标准的基本功能,而 MySQL 支持更广泛的 SQL 标准以及许多扩展功能。 SQLite3 的数据存储在单个文件中,而 MySQL 的数据通常存储在多个文件或分布式系统中。 就记录这么多吧!