【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.pswp.cn/diannao/83116.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83116.shtml
英文地址,請注明出處:http://en.pswp.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 研究背景與意義 在當今數字化時代,網絡數據蘊含著巨大的價值。然而,許多網站為了保護其數據安全和商業利益,會采用各種加密手段對傳輸的數據進行處理。這些加密措施給數據采集工作帶來了巨大挑戰。網絡爬蟲逆向解密技術應運而生,它通過分析和破解網站的加密機…