【Linux C/C++开发】轻量级关系型数据库SQLite开发(包含性能测试代码)

前言

        之前的文件分享过基于内存的STL缓存、环形缓冲区,以及基于文件的队列缓存mqueue、hash存储、向量库annoy存储,这两种属于比较原始且高效的方式。

        那么,有没有高级且高效的方式呢。有的,从数据角度上看,(封装好底层的)SQL语法开发就是一种成熟、高效的方式。

        本文主要讲解的是不需要搭建服务器的数据库类型(支持SQL语法)的轻量级缓存数据库SQLite。

SQLite特性

  • ​​单文件存储​​:整个数据库(表、索引、视图等)存储在一个文件中,便于移植和备份;
  • 无服务器架构​​:无需独立服务进程,通过函数调用操作数据;
  • 资源占用低​​:库文件仅约 1MB,内存消耗少,适合资源受限环境;
  • 高性能​​:读写操作直接访问磁盘文件,省去网络开销,响应速度快;
  • 跨平台兼容​​:支持所有主流操作系统(Windows、Linux、macOS、Android、iOS等),文件格式统一,跨平台迁移无需转换;

局限性​

  • ​并发写入​​:同一时间仅支持单线程写入,高并发写场景性能受限;
  • ​无用户管理​​:缺乏内置的用户权限系统,依赖文件系统权限;
  • 数据规模​​:单库文件理论上支持 TB 级数据,但更适合中小规模应用;

使用场景

        从以上特性及局限性可以看出sqlite是适合本地调用的GB级别数据库,可以很好的支持资源受限的移动应用、嵌入式设备,以及访问量较低的中小型网站、缓存数据量较低的应用场景()。

        特别适合这种场景:比如计划安装redis、mysql来搭建SQL服务时,经过评估每日数据量,一个表都达不到100万条数据的情况下,可以直接选用SQLite提供数据库存储。

        这里要额外的说明,SQLite本身不支持网络访问,如果是C/S架构,可以在服务器端搭建的网络服务中间件(比如通过前端、python、C/C++ socket方式提供网络服务)。

详细讲解

        本文的源码是在国产桌面操作系统(统信UOS和麒麟kylin系统)下进行编译开发的,默认安装有sqlite3,如果没有安装,可以用以下命令安装:

sudo apt install sqlite3 libsqlite3-dev

命令行操作

安装完成之后,可以在命令行查看库文件:

sqlite3库开发

本文分享的源码中,在SQLiteDB类中封装了打开数据库、创建表、插入表、批量插入表、更新表、查询表的功能,其中批量插入表提供批量插入模板(支持传结构体),文件结构如下:

mysqlite.h

#ifndef MY_SQLITE_H
#define MY_SQLITE_H
#include <sqlite3.h>
#include <iostream>
#include <list>
#include <vector>
#include <map>
#include <string>
#include <set>// 查询结果的数据结构
typedef struct QueryResult {void* data;  //指向C++的QueryResult对象
} QueryResult;class SQLiteDB {
public:// 构造函数/析构函数explicit SQLiteDB(const std::string& dbname);~SQLiteDB();// 基础操作int createTable(const char* sql);// 创建表int insertTable(const char* sql);// 插入数据int updateTable(const char* sql);// 更新数据int queryTable(const char* sql , QueryResult* result);// 查询数据void closedb();// 关闭数据库//批量插入模板template<typename Container, typename BindFunc>int batchInsertTemplate(const char* insert_sql,const Container& container,BindFunc binder){// 使用预编译语句优化插入性能sqlite3_stmt* stmt = nullptr;// 准备预编译语句if (sqlite3_prepare_v2(sql_db, insert_sql, -1, &stmt, nullptr) != SQLITE_OK) {std::cerr << "准备预编译语句失败: " << sqlite3_errmsg(sql_db) << std::endl;return -1;}setcache();beginTransaction(); // 开启事务int success_count = 0;int batch_size = 1000; // 每批插入量try {int counter = 0;for (const auto& item : container) {// 绑定参数binder(stmt, item);// 执行插入int rc = sqlite3_step(stmt);if (rc != SQLITE_DONE) {//std::cerr << "插入数据失败[" << counter << "]: "//          << sqlite3_errmsg(db) << " Phone: " << user.phone << std::endl;// 继续尝试下一条而非直接返回} else {success_count++;}sqlite3_reset(stmt);counter++;// 分批提交if (counter % batch_size == 0) {commitTransaction();// 提交记录beginTransaction();  // 开启新事务}}// 提交剩余记录commitTransaction();// 提交最后一批可能不足1000条的记录} catch (...) {rollbackTransaction();sqlite3_finalize(stmt);// 释放预编译语句throw; // 重新抛出异常}// 释放预编译语句sqlite3_finalize(stmt);return success_count;}//读取结果QueryResult* createQueryResult();void freeQueryResult(QueryResult* result);//释放QueryResult内存const std::list<std::string>& getColumnNames(const QueryResult* result);const std::vector<std::map<int, std::string>>& getRows(const QueryResult* result);bool m_status;
private:bool opendb(const char* dbname);int execsql(const char* sql);// 实现事务函数int beginTransaction();int commitTransaction();int rollbackTransaction();int setcache();sqlite3* sql_db;
};#endif // MY_SQLITE_H

mysqlite.cpp

#include "mysqlite.h"struct InternalQueryResult {std::list<std::string> columnNames;std::vector<std::map<int, std::string>> rows;
};SQLiteDB::SQLiteDB(const std::string& dbname){opendb(dbname.c_str());
}SQLiteDB::~SQLiteDB(){}bool SQLiteDB::opendb(const char* dbname) {m_status=true;int rc = sqlite3_open(dbname, &sql_db);if (rc != SQLITE_OK) {//std::cerr << "无法打开数据库: " << sqlite3_errmsg(db) << std::endl;sqlite3_close(sql_db); // 打开失败时关闭数据库m_status = false;//return m_status;}return m_status;
}int SQLiteDB::execsql(const char* sql) {char* errMsg = nullptr;int rc = sqlite3_exec(sql_db, sql, nullptr, nullptr, &errMsg);if (rc != SQLITE_OK) {sqlite3_free(errMsg);//std::cerr << "执行失败: " << errMsg << std::endl;return -1;}return 0;
}int SQLiteDB::createTable(const char* sql) {int ret=execsql(sql);if(ret==-1) std::cerr << "创建表失败: " << std::endl;else std::cout << "创建表成功" << std::endl;return ret;
}int SQLiteDB::insertTable( const char* sql) {int ret=execsql(sql);//if(ret==-1) std::cerr << "插入数据失败: " << std::endl;//else std::cout << "插入数据成功" << std::endl;return ret;
}int SQLiteDB::updateTable( const char* sql) {int ret=execsql(sql);//if(ret==-1) std::cerr << "更新数据失败: " << std::endl;//else std::cout << "更新数据成功" << std::endl;return ret;
}// 回调函数:将数据存储到QueryResult中
static int callback(void* data, int argc, char** argv, char** colName) {InternalQueryResult* retdata = static_cast<InternalQueryResult*>(data);// 保存列名(只在第一次回调时保存)if (retdata->columnNames.empty()) {for (int i = 0; i < argc; i++) {retdata->columnNames.push_back(colName[i]);}}// 保存当前行数据std::map<int, std::string> row;for (int i = 0; i < argc; i++) {row[i] = argv[i] ? argv[i] : "NULL";  // 键=列索引,值=数据}retdata->rows.push_back(row);return 0;
}// 创建QueryResult对象
QueryResult* SQLiteDB::createQueryResult() {auto* result = new QueryResult;result->data = new InternalQueryResult;return result;
}// 释放QueryResult内存
void SQLiteDB::freeQueryResult(QueryResult* result) {if (result) {delete static_cast<InternalQueryResult*>(result->data);delete result;}
}// 获取列名列表
const std::list<std::string>& SQLiteDB::getColumnNames(const QueryResult* result) {return static_cast<InternalQueryResult*>(result->data)->columnNames;
}// 获取行数据
const std::vector<std::map<int, std::string>>& SQLiteDB::getRows(const QueryResult* result) {return static_cast<InternalQueryResult*>(result->data)->rows;
}int SQLiteDB::queryTable(const char* sql , QueryResult* result) {char* errMsg = nullptr;auto* internalResult = static_cast<InternalQueryResult*>(result->data);int rc = sqlite3_exec(sql_db, sql, callback, internalResult, &errMsg);if (rc != SQLITE_OK) {std::cerr << "查询失败: " << errMsg << std::endl;sqlite3_free(errMsg);return -1;}return 0;
}void SQLiteDB::closedb() {sqlite3_close(sql_db);
}// 实现事务模式
int SQLiteDB::beginTransaction() {return sqlite3_exec(sql_db, "BEGIN IMMEDIATE TRANSACTION;", nullptr, nullptr, nullptr);
}int SQLiteDB::setcache() {return sqlite3_exec(sql_db, "PRAGMA cache_size=10000;", nullptr, nullptr, nullptr);
}
//
int SQLiteDB::commitTransaction() {return sqlite3_exec(sql_db, "COMMIT;", nullptr, nullptr, nullptr);
}int SQLiteDB::rollbackTransaction() {return sqlite3_exec(sql_db, "ROLLBACK;", nullptr, nullptr, nullptr);
}

引用类的主程序main.cpp

#include "mysqlite.h"
#include <string>
#include <iostream>
#include <chrono>
#include <random>
#include <set>
// 用户结构体定义
typedef struct user_st {std::string phone;std::string name;std::string sex;// 用于set排序的比较函数bool operator<(const user_st& other) const {return phone < other.phone;}
} user_st;// 准备绑定函数
auto stBinder = [](sqlite3_stmt* stmt, const user_st& p) {return sqlite3_bind_text(stmt, 1, p.phone.c_str(), -1, SQLITE_TRANSIENT) == SQLITE_OK &&sqlite3_bind_text(stmt, 2, p.name.c_str(), -1, SQLITE_TRANSIENT) == SQLITE_OK &&sqlite3_bind_text(stmt, 3, p.sex.c_str(), -1, SQLITE_TRANSIENT) == SQLITE_OK;
};// 生成随机手机号 (135xxxxxxxx)
std::string generatePhone() {std::random_device rd;std::mt19937 gen(rd());std::uniform_int_distribution<> dis(0, 99999999);char buf[12];snprintf(buf, sizeof(buf), "135%08d", dis(gen));return buf;
}int main() {// 打开数据库SQLiteDB litedb("massive_data.db");if (!litedb.m_status) {std::cerr << "无法打开数据库" << std::endl;return -1;}if(0)//创建表并批量插入测试{// 创建表(如果不存在)const char* createTableSQL ="CREATE TABLE IF NOT EXISTS users (""phone TEXT PRIMARY KEY NOT NULL,""name TEXT,""sex TEXT CHECK(sex IN ('M', 'F'))"");";if (litedb.createTable(createTableSQL) != 0) {std::cerr << "创建表失败" << std::endl;litedb.closedb();return -1;}std::set<user_st> user_set;for (int i = 0; i < 1000000; ++i) {user_st usermsg;usermsg.phone = generatePhone();usermsg.name = "User_" + std::to_string(i);usermsg.sex = (i % 2) ? "M" : "F";user_set.insert(usermsg);}//插入100万条数据(批量事务优化)std::cout << "开始插入100万条数据..." << std::endl;auto startInsert = std::chrono::high_resolution_clock::now();const char* insert_sql = "INSERT OR IGNORE INTO users (phone, name, sex) VALUES (?, ?, ?);";litedb.batchInsertTemplate(insert_sql,user_set,stBinder);auto endInsert = std::chrono::high_resolution_clock::now();std::chrono::duration<double> insertTime = endInsert - startInsert;std::cout << "插入完成,耗时: " << insertTime.count() << "秒" << std::endl;}{//查询数据std::cout << "测试100万条数据查询速度..." << std::endl;auto startquery = std::chrono::high_resolution_clock::now();QueryResult* result = litedb.createQueryResult();const char* sqlstr = "SELECT * FROM users WHERE phone='13555927718';";std::vector<std::string> colname;if (litedb.queryTable(sqlstr,result) == 0) {// 打印列名std::cout << "列名: ";for (const auto& col : litedb.getColumnNames(result)) {std::cout << col << " ";colname.push_back(col);}std::cout << std::endl;// 打印所有行数据int rowNum = 0;for (const auto& row : litedb.getRows(result)) {std::cout << "行 " << rowNum++ << ": ";for (const auto& pair : row) {//std::cout << pair.first << "=" << pair.second << " ";std::cout << colname[pair.first] << "=" << pair.second << " ";}std::cout << std::endl;}}litedb.freeQueryResult(result);auto endquery = std::chrono::high_resolution_clock::now();std::chrono::duration<double> duration = endquery - startquery;std::cout << "查询完成,耗时: " << duration.count() << "秒" << std::endl;}litedb.closedb();return 0;
}

Makefile文件

CC        = g++
CFLAGS    =
DEBUGFLAG = -g -Wall
SRCS      = $(wildcard *.cpp)
LIBDIRS   = -L./ 
LIBS      = -lsqlite3 
INCLUDE   = -I. 
TARGET    = sqlitetest
OBJS      = $(SRCS:.cpp=.o)all: $(TARGET)# 生成可执行文件
$(TARGET): $(OBJS)$(CC) $(LIBS) $^ -o $@# 通用规则:编译 .cpp 文件为 .o 文件
%.o: %.cpp$(CC) $(LIBS) -c $< -o $@clean:rm -f $(OBJS) $(TARGET).PHONY: all clean

测试效果图:

        通过以上的批量插入和查询性能测试,我的国产信创终端设备,插入100万条速度15秒,查询速度是0.6毫秒,满足我的QT应用及后台应用开发需求(1条处理不超过1000条数据)。

       sqlite默认是使用 B+ Tree作为其主要的索引结构(与MySQL一样)能够进行高效的范围查询。另外

        其他分享:之前分享的hash存储查询方式,是在同一台国产设备上运行的,100万条记录查询1条记录速度是1微妙,比sqlite要强很多,适合TB级数据处理。hash存储查询https://blog.csdn.net/liangyuna8787/article/details/147492363?spm=1001.2014.3001.5501

篇尾

        sqlite的轻量级数据库特性,不仅易于部署,并且容易移植,且维护成本低,适合对数据库依赖性低的项目,C/C++项目本身就对数据库依赖低,所以本文专门分享从性能测试角度来选型sqlite。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/83116.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

首个专业AI设计Agent发布-Lovart

Lovart是什么 Lovart 是为设计师打造的世界上首个专业设计 Agent。Lovart 能像专业设计师一样思考和执行设计任务&#xff0c;提供高水平的设计方案。基于自然语言交互&#xff0c;用户能快速调整布局、颜色和构图。Lovart 支持从创意拆解到专业交付的全链路设计&#xff0c;单…

关于Python 实现接口安全防护:限流、熔断降级与认证授权的深度实践

作为一名IT从业者&#xff0c;就自己的职业经历&#xff0c;我一直很注重系统安全的。从桌面时代就对此很感兴趣&#xff0c;后来随着技术的更新迭代&#xff0c;系统安全衍生出来了网络安全。维度更大&#xff0c;范围更广。尤其在数字化浪潮席卷全球的今天&#xff0c;互联网…

onGAU:简化的生成式 AI UI界面,一个非常简单的 AI 图像生成器 UI 界面,使用 Dear PyGui 和 Diffusers 构建。

​一、软件介绍 文末提供程序和源码下载 onGAU&#xff1a;简化的生成式 AI UI界面开源程序&#xff0c;一个非常简单的 AI 图像生成器 UI 界面&#xff0c;使用 Dear PyGui 和 Diffusers 构建。 二、Installation 安装 文末下载后解压缩 Run install.py with python to setup…

南方科技大学Science! 自由基不对称催化新突破 | 乐研试剂

近日&#xff0c;南方科技大学刘心元教授团队联合浙江大学洪鑫教授团队在自由基不对称催化领域取得新进展。课题组开发了一系列大位阻阴离子 N,N,P-配体&#xff0c;用于铜催化未活化外消旋仲烷基碘与亚砜亚胺的不对称胺化反应。该反应表现出广泛的底物兼容性&#xff0c;涵盖具…

Milvus 视角看主流嵌入式模型(Embeddings)

嵌入是一种机器学习概念&#xff0c;用于将数据映射到高维空间&#xff0c;其中语义相似的数据被紧密排列在一起。嵌入模型通常是 BERT 或其他 Transformer 系列的深度神经网络&#xff0c;它能够有效地用一系列数字&#xff08;称为向量&#xff09;来表示文本、图像和其他数据…

【MySQL】牛客网sql语句简单例题,sql入门

目录 一、基础查询 1、查询所有列 2、 查询多列 二、简单处理查询结果 1、查询结果去重 2、查询结果限制返回列数 3、将查询后的列重新命名 三、条件查询之基础排序 1、查找后排序 2、 查找后多列排序 3、查找后降序排列 四、条件查询之基础操作符 1、查找学校是北…

Linux云计算训练营笔记day06(Windows DOS下的常用命令 及 HTML)

windows dos命令行 切换盘符 d: 查看文件夹下的内容 dir 创建文件夹 md/mkdir gongli 进入文件夹 cd gongli 往回退一层 cd .. 清屏 cls 历史命令(用键盘的上下键) 创建一个空的文件 echo.>a.txt 写入内容到文件中 echo hello world > b.txt 删除文件 del a.txt 查…

如何开启或关闭WordPress的自动更新功能

WordPress是一个开源软件&#xff0c;您可以从他们的官方网站免费下载。但是&#xff0c;要启动WordPress站点&#xff0c;您需要安装一个主题&#xff0c;以帮助为您的内容创建特定布局。此外&#xff0c;您可能还需要安装一些插件来添加其他功能。 当您必须管理所有这些东西…

SpringSecurity当中的CSRF防范详解

CSRF防范 什么是CSER 以下是基于 CSRF 攻击过程的 顺序图 及详细解释&#xff0c;结合多个技术文档中的攻击流程&#xff1a; CSRF 攻击顺序图 #mermaid-svg-FqfMBQr8DsGRoY2C {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#m…

给 DBGridEh 增加勾选用的检查框 CheckBox

需求 Delphi 的 DBGrid 通过 DataSource 绑定到一个 DataSet 显示数据表里面的 N 多条记录。如果我想给每条记录加一个 CheckBox 让用户去勾选&#xff0c;该怎么做&#xff1f; 以下描述&#xff0c;使用的 DBGrid 是 DBGrieEh。 Delphi 自带的 DBGrid 要加 CheckBox 比较麻…

WordPress 和 GPL – 您需要了解的一切

如果您使用 WordPress&#xff0c;GPL 对您来说应该很重要&#xff0c;您也应该了解它。查看有关 WordPress 和 GPL 的最全面指南。 您可能听说过 GPL&#xff08;通常被称为 WordPress 的权利法案&#xff09;&#xff0c;但很可能并不完全了解它。这是有道理的–这是一个复杂…

力扣144题:二叉树的前序遍历(递归)

小学生一枚&#xff0c;自学信奥中&#xff0c;没参加培训机构&#xff0c;所以命名不规范、代码不优美是在所难免的&#xff0c;欢迎指正。 标签&#xff1a; 二叉树、前序遍历、递归 语言&#xff1a; C 题目&#xff1a; 给你二叉树的根节点root&#xff0c;返回它节点值…

python:一个代理流量监控的媒体文件下载脚本

前言 一个mitmproxy代理服务应用&#xff0c;作用是监听系统流量&#xff0c;并自动下载可能的video媒体文件到本地。 如果你没有安装mitmproxy或没有做完准备工作&#xff0c;请参考我的这篇文章&#xff1a; python&#xff1a;mitmproxy代理服务搭建-CSDN博客 文件架构目录…

SAP Business One(B1)打开自定义对象报错【Failed to initialize document numbering:】

业务场景&#xff1a; 新版本的客户端&#xff0c;打开已经注册的自定义单据类型的表的时候&#xff0c;报错【Failed to initialize document numbering:】。 但是注册的自定义主数据类型的表&#xff0c;不会有问题。 解决方案&#xff1a; 打开【管理-系统初始化-常规设置…

计算机网络:WiFi路由器发射的电磁波在空气中的状态是什么样的?

WiFi路由器发射的电磁波是高频无线电波,属于微波频段(2.4GHz或5GHz),在空气中以光速传播(约310⁸米/秒),其传播状态和特性可通过以下维度详细解析: 一、电磁波的物理特性 频率与波长 2.4GHz频段:波长约12.5厘米,穿透力较强但易受干扰(微波炉、蓝牙等共用频段)。5GH…

腾讯云-人脸核身+人脸识别教程

一。产品概述 慧眼人脸核身特惠活动 腾讯云慧眼人脸核身是一组对用户身份信息真实性进行验证审核的服务套件&#xff0c;提供人脸核身、身份信息核验、银行卡要素核验和运营商类要素核验等各类实名信息认证能力&#xff0c;以解决行业内大量对用户身份信息真实性核实的需求&a…

tocmat 启动怎么设置 jvm和gc

在生产环境中部署 Java Web 应用时&#xff0c;我们经常需要给 Tomcat 设置 JVM 参数和 GC 策略&#xff0c;以提高性能、稳定性和可观察性。以下是完整教程&#xff1a; 一、Tomcat 设置 JVM 启动参数的方式 1. 修改 startup 脚本&#xff08;推荐&#xff09; 以 Linux 系统…

zuoyyyeee

实验拓扑图 需求分析 1.分配接口ip 2.使用OSPF协议使三台路由器可达 3.在路由器1&#xff0c;2 /4&#xff0c;5 使用直连接口直接配置EBGP ip配置&#xff1a; [R1]: bgp 100 rid 1.1.1.1 peer 12.0.0.2 as-number 200 network 1.1.1.1 32 [R2]: bgp 200 rid 2.2.2.2 p…

‌Element UI 双击事件(@cell-dblclick 与 @row-dblclick)

‌Element UI 双击事件&#xff08;cell-dblclick 与 row-dblclick&#xff09; 一、核心双击事件绑定‌ 表格单元格双击‌ ‌事件绑定‌&#xff1a; 通过 cell-dblclick 监听单元格双击&#xff0c;接收四个参数&#xff08;row, column, cell, event&#xff09;。 ‌示…

Python爬虫实战:研究decrypt()方法解密

1. 引言 1.1 研究背景与意义 在当今数字化时代,网络数据蕴含着巨大的价值。然而,许多网站为了保护其数据安全和商业利益,会采用各种加密手段对传输的数据进行处理。这些加密措施给数据采集工作带来了巨大挑战。网络爬虫逆向解密技术应运而生,它通过分析和破解网站的加密机…