前言
? ? ? ? 之前的文件分享過基于內存的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。