前言
想写个demo, 由C程序写sqlite3数据库中的表A, 由java程序去读sqlite3数据库中的表A.
这样,就涉及到sqlite3的线程安全和进程安全访问。
写好测试程序后,跑起来,很失望。
sqlite3同一个表无法由2个进程同时读写表记录。
总有一个进程会出现“database is locked”的报错,去查了资料。大概意思是:因为是文件型数据库,无法使2个进程同时向同一个文件写数据。我的应用是一个读(读完一条记录删一条),一个写,应该会触发sqlite的文件读写锁吧。
尝试sql执行失败后重试,倒是可以继续跑。但是每一个SQL都有可能失败,都要重试。这执行效率就下来了。
换其他数据库了,失望。
其实,sqlite作者是可以解决这个问题的。只需要封装sqlite3_xx的接口实现不去直接操作文件,而是由同一个数据库代理程序(第一次打开数据库时创建数据库代理程序的实例)访问数据库文件,这样,就可以达到多个进程共享一个数据库文件,数据库的打开和关闭,带上引用计数就好。改动的代码也不大。谁知道sqlite作者咋想的呢,哪个数据库的使用者没有多进程访问同一个数据库的需求呢?
如果使用者非要使用sqlite3, 来支持多进程安全访问。只能自己再封装一组接口,由自己的程序来控制数据库文件的访问(同一个数据库文件只有一个访问者,对使用者隐藏了数据库文件的操作). sqlite3自己可以保证线程安全性。这样也能进程安全,其实这活应该由sqlite3数据库来完成。如果使用者要想那么多,谁还来用这么难用的数据库。毕竟大部分数据库都是可以进程安全的。
说来说去,其实sqlite3需要加一个服务程序,那些sqlite3_xx接口,都是通过本地socket来向服务程序读写数据,这样就正常了。这样改动也不大。
也许sqlite的需求场景,就是给一个程序用吧。
demo下载点
src_test_sqlite_process_safe.7z
demo预览
# @file README
# 下载 sqlite-autoconf-3250200.tar.gz
# tar -xzvf sqlite-autoconf-3250200.tar.gz
# cd sqlite-autoconf-3250200
# ./configure ./configure --prefix /home/sqlite3_was_install
# make
# make install
# 将 /home/sqlite3_was_install 拷贝到工程的doc目录
// @file main.cpp
// @brief 测试sqlite3的进程安全性访问
// @note
// 实验环境:
// debian8.8
// 实验结论:
// sqlite3做的渣, 不支持多进程访问同一个数据库的同一个表中的记录
// 当多进程同时高频度读写同一个表时, 任何一句sql都可能执行失败, 失望
// 当一句正常的sql执行失败时,总不能去无限的重试吧,那还叫数据库么?
//
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <errno.h>
#include "const_define.h"
#include "sqlite3.h"
void init(const char* psz_log_owner_name);
void uninit();
void proc_sig_term(int num);
int fn_test(int argc, char** argv);
// @ref https://www.tutorialspoint.com/sqlite/sqlite_c_cpp.htm
int fn_sqlite_db_init();
int fn_test_sqlite_read();
int fn_test_sqlite_write();
typedef int (*PFN_cb_sqlite_proc)(void* cb_info, int argc, char **argv, char **azColName);
int cb_sqlite_proc_select(void* cb_info, int argc, char **argv, char **azColName);
int cb_sqlite_proc_insert(void* cb_info, int argc, char **argv, char **azColName);
bool fn_open_db(sqlite3*& db);
void fn_close_db(sqlite3*& db);
void fn_free_sqlite_msg(char*& psz_msg);
bool fn_open_or_create_tbl(sqlite3* db, const char* psz_tbl_name, const char* psz_field_declare);
// sqlite sql example
// CREATE TABLE TBL_NAME(ID INT PRIMARY KEY NOT NULL,NAME TEXT NOT NULL,AGE INT NOT NULL,ADDRESS CHAR(50),SALARY REAL);
// INSERT INTO TBL_NAME(ID,NAME,AGE,ADDRESS,SALARY) VALUES (1, 'Paul', 32, 'California', 20000.00 );
// DELETE from TBL_NAME where ID=2;
// UPDATE TBL_NAME set SALARY = 25000.00 where ID=1;;
// SELECT * from TBL_NAME;
bool fn_sql_exec(
sqlite3* db,
const char* psz_sql,
std::string& str_err_msg,
TAG_SQLITE3_CALLBACK_INFO* p_cb_info,
PFN_cb_sqlite_proc pfn_cb);
bool is_file_exist(const char* psz_file_path_name, int& i_err_code, std::string& str_err_msg);
int main(int argc, char** argv)
{
char sz_buf[MAX_MSG_LENGTH] = {'\0'};
#ifdef MAKE_FILE_MACRO__BIN_NAME
sprintf(sz_buf, "%s", MAKE_FILE_MACRO__BIN_NAME);
init(sz_buf);
MYLOG_D("MAKE_FILE_MACRO__BIN_NAME = [%s]\n", MAKE_FILE_MACRO__BIN_NAME);
#else
init(NULL);
#endif // #ifdef MAKE_FILE_MACRO__BIN_NAME
fn_test(argc, argv);
uninit();
MYLOG_D("THE END\n");
return EXIT_SUCCESS;
}
void uninit()
{
}
void proc_sig_term(int num)
{
MYLOG_D("SIGTERM = %d, num = %d\n", SIGTERM, num);
MYLOG_D("maybe can do some clean task before quit\n");
exit(1);
}
void init(const char* psz_log_owner_name)
{
int i = 0;
// daemon(0, 0);
// clear screen (print 25 empty line)
for (i = 0; i < 25; i++) {
MYLOG_D("\n");
}
signal(SIGTERM, proc_sig_term);
}
int fn_test(int argc, char** argv)
{
bool b_rc = false;
MYLOG_D(">> fn_test()\n");
do {
if (2 != argc) {
break;
}
fn_sqlite_db_init();
b_rc = true;
switch (argv[1][0]) {
case 'r':
fn_test_sqlite_read();
break;
case 'w':
fn_test_sqlite_write();
break;
default:
b_rc = false;
break;
}
if (!b_rc) {
break;
}
b_rc = true;
} while (0);
if (!b_rc) {
MYLOG_D("usage : this_program r(ead)/w(rite)\n");
MYLOG_D("e.g. this_program r\n");
MYLOG_D("e.g. this_program w\n");
}
return 0;
}
int cb_sqlite_proc_insert(void* cb_info, int argc, char **argv, char **azColName) {
int i = 0;
TAG_SQLITE3_CALLBACK_INFO* p_cb_info = (TAG_SQLITE3_CALLBACK_INFO*)cb_info;
TAG_COL_NAME_AND_VALUE name_value;
if (NULL != p_cb_info) {
p_cb_info->clear();
for(i = 0; i < argc; i++) {
name_value.set(i, azColName[i], argv[i]);
p_cb_info->vec_result.push_back(name_value);
}
}
return 0; // return 0, to continue sql exec
}
int cb_sqlite_proc_select(void* cb_info, int argc, char **argv, char **azColName) {
int i = 0;
TAG_SQLITE3_CALLBACK_INFO* p_cb_info = (TAG_SQLITE3_CALLBACK_INFO*)cb_info;
TAG_COL_NAME_AND_VALUE name_value;
if (NULL != p_cb_info) {
p_cb_info->clear();
for(i = 0; i < argc; i++) {
name_value.set(i, azColName[i], argv[i]);
p_cb_info->vec_result.push_back(name_value);
}
}
// i want to read only one row set
return 1; // return not 0, to stop sql exec
}
bool fn_open_db(sqlite3*& db)
{
int i_rc = 0;
// Open database
// sqlite3_open and sqlite3_open_v2 : db name is utf-8
i_rc = sqlite3_open_v2(
SQLITE_OBJ_DATABASE_PATH_NAME, // Database filename (UTF-8)
&db, // OUT: SQLite db handle
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_SHAREDCACHE, // Flags
NULL // Name of VFS module to use
);
// @note
// 一个程序读,一个程序写时,出现了如下错误
// fn_sql_exec error = [database is locked]
// 读的程序和写的程序如果在读失败和写失败后,继续尝试执行各自读和写的sql, 会有成功的一天
// 但是这样就有问题了, 我写失败,难道我还要重试? 谁知道要尝试多少次才会成功?
// 每执行一次SQL都有可能失败,这谁受的了:(
// 由于多进程的读写,即使加上SQL执行失败的重试, 也会使sqlite数据库的访问变成串行的了. 渣...
return (SQLITE_OK == i_rc);
}
void fn_close_db(sqlite3*& db)
{
if (NULL != db) {
sqlite3_close(db);
db = NULL;
}
}
bool fn_open_or_create_tbl(sqlite3* db, const char* psz_tbl_name, const char* psz_field_declare)
{
int i_rc = SQLITE_ERROR;
char sz_buf[MAX_SQL_LENGTH] = {'\0'};
std::string str_err_msg = "";
TAG_SQLITE3_CALLBACK_INFO cb_info;
TAG_COL_NAME_AND_VALUE name_value;
do {
if ((NULL == db)
|| (NULL == psz_tbl_name)
|| (NULL == psz_field_declare))
{
break;
}
// check tbl is exist
sprintf(sz_buf,
"select count(type) from sqlite_master where type='table' and name='%s'",
psz_tbl_name);
if (!fn_sql_exec(db, sz_buf, str_err_msg, &cb_info, cb_sqlite_proc_insert)) {
MYLOG_D("1 fn_sql_exec error = [%s]\n", str_err_msg.c_str());
break;
}
if (cb_info.vec_result.size() <= 0) {
break;
}
name_value = cb_info.pop_first();
if (name_value.str_value == "1") {
// tbl is exist
i_rc = SQLITE_OK;
break;
}
// table not exist, create it
sprintf(sz_buf,
"CREATE TABLE %s(%s);",
psz_tbl_name,
psz_field_declare);
if (!fn_sql_exec(db, sz_buf, str_err_msg, &cb_info, cb_sqlite_proc_insert)) {
MYLOG_D("2 fn_sql_exec error = [%s]\n", str_err_msg.c_str());
break;
}
i_rc = SQLITE_OK;
MYLOG_D("table created successfully\n");
} while (0);
return (SQLITE_OK == i_rc);
}
void fn_free_sqlite_msg(char*& psz_msg)
{
if (NULL != psz_msg) {
sqlite3_free(psz_msg);
psz_msg = NULL;
}
}
int fn_test_sqlite_read()
{
sqlite3* db = NULL;
char sz_buf[MAX_SQL_LENGTH] = {'\0'};
int i_err_code = 0;
std::string str_err_msg;
TAG_SQLITE3_CALLBACK_INFO cb_info;
TAG_COL_NAME_AND_VALUE name_value;
do {
if (!fn_open_db(db)) {
MYLOG_D("can't open database: %s\n", sqlite3_errmsg(db));
break;
}
MYLOG_D("opened database successfully\n");
sprintf(sz_buf, "%s",
"ID INT PRIMARY KEY NOT NULL," \
"NAME TEXT NOT NULL," \
"AGE INT NOT NULL," \
"ADDRESS CHAR(50)," \
"SALARY REAL");
if (!fn_open_or_create_tbl(db, SQLITE_OBJ_TABLE_NAME, sz_buf)) {
break;
}
do {
if (is_file_exist(FLAG_FILE_TO_STOP_READ, i_err_code, str_err_msg)) {
MYLOG_D("find stop flag file[%s], stop read now\n", FLAG_FILE_TO_STOP_READ);
break;
}
sprintf(sz_buf,
"SELECT * from %s;",
SQLITE_OBJ_TABLE_NAME);
if (!fn_sql_exec(db,
sz_buf,
str_err_msg,
&cb_info,
cb_sqlite_proc_select))
{
MYLOG_D("3 fn_sql_exec error : %s\n", str_err_msg.c_str());
} else {
if (cb_info.vec_result.size() > 0) {
cb_info.show_result();
// process row set was read back
// ...
// delete it
name_value = cb_info.pop_first();
if (name_value.str_name == "ID") {
sprintf(sz_buf,
"DELETE from %s where ID=%s;",
SQLITE_OBJ_TABLE_NAME,
name_value.str_value.c_str());
if (!fn_sql_exec(db,
sz_buf,
str_err_msg,
&cb_info,
cb_sqlite_proc_select))
{
MYLOG_D("delete failed : %s\n", str_err_msg.c_str());
} else {
MYLOG_D("delete one row ok\n");
}
} else {
MYLOG_D("can't find first col name is \"ID\"\n");
}
}else {
MYLOG_D("can't read any row set\n");
}
}
} while (1);
} while (0);
fn_close_db(db);
return 0;
}
int fn_test_sqlite_write()
{
sqlite3* db = NULL;
char sz_buf[MAX_SQL_LENGTH] = {'\0'};
int i_err_code = 0;
std::string str_err_msg;
int i_id = 0;
TAG_SQLITE3_CALLBACK_INFO cb_info;
do {
if (!fn_open_db(db)) {
MYLOG_D("can't open database: %s\n", sqlite3_errmsg(db));
break;
}
MYLOG_D("opened database successfully\n");
sprintf(sz_buf, "%s",
"ID INT PRIMARY KEY NOT NULL," \
"NAME TEXT NOT NULL," \
"AGE INT NOT NULL," \
"ADDRESS CHAR(50)," \
"SALARY REAL");
if (!fn_open_or_create_tbl(db, SQLITE_OBJ_TABLE_NAME, sz_buf)) {
break;
}
do {
if (is_file_exist(FLAG_FILE_TO_STOP_WRITE, i_err_code, str_err_msg)) {
MYLOG_D("find stop flag file[%s], stop write now\n", FLAG_FILE_TO_STOP_WRITE);
break;
}
sprintf(sz_buf,
"INSERT INTO %s (ID,NAME,AGE,ADDRESS,SALARY) VALUES (%d, 'Paul', 32, 'California', 20000.00 );",
SQLITE_OBJ_TABLE_NAME,
i_id++);
if (!fn_sql_exec(db,
sz_buf,
str_err_msg,
&cb_info,
cb_sqlite_proc_insert))
{
MYLOG_D("4 fn_sql_exec error : %s\n", str_err_msg.c_str());
}
MYLOG_D("insert ok, id = %d\n", i_id - 1);
cb_info.show_result();
} while (1);
} while (0);
fn_close_db(db);
return 0;
}
bool fn_sql_exec(
sqlite3* db,
const char* psz_sql,
std::string& str_err_msg,
TAG_SQLITE3_CALLBACK_INFO* p_cb_info,
PFN_cb_sqlite_proc pfn_cb)
{
int i_rc = SQLITE_ERROR;
char sz_buf[MAX_SQL_LENGTH] = {'\0'};
char* psz_err_msg = NULL;
do {
str_err_msg.clear();
if ((NULL == db) || (NULL == psz_sql)) {
break;
}
strcpy(sz_buf, psz_sql);
i_rc = sqlite3_exec(db, sz_buf, pfn_cb, p_cb_info, &psz_err_msg);
if ((SQLITE_OK != i_rc) && (SQLITE_ABORT != i_rc)) {
str_err_msg = psz_err_msg;
fn_free_sqlite_msg(psz_err_msg);
break;
}
} while (0);
return ((SQLITE_OK == i_rc) || (SQLITE_ABORT == i_rc));
}
int fn_sqlite_db_init()
{
int i_rc = 0;
MYLOG_D("sqlite3_libversion() = [%s]\n", sqlite3_libversion());
MYLOG_D("sqlite3_libversion_number() = [%d]\n", sqlite3_libversion_number());
MYLOG_D("sqlite3_sourceid() = [%s]\n", sqlite3_sourceid());
sqlite3_shutdown();
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
i_rc = sqlite3_threadsafe();
// SQLITE_THREADSAFE = 1 or 2, but not 0(0 is not thread safe)
// 0 is 单线程模式
// 1 is 串行模式
// 2 is 多线程模式
MYLOG_D("sqlite3_threadsafe() = %d\n", i_rc);
if (i_rc > 0) {
i_rc = sqlite3_config(SQLITE_CONFIG_SERIALIZED);
MYLOG_D("sqlite3_config() = %d\n", i_rc);
if (SQLITE_OK == i_rc) {
MYLOG_D("can now use sqlite on multiple threads, using the same connection\n");
} else {
MYLOG_D("!! warning : setting sqlite thread safe mode to serialized failed\n");
}
} else {
MYLOG_D("!! warning : current sqlite database is not compiled to be threadsafe\n");
}
sqlite3_initialize();
// 当多进程一起访问同一个数据库文件时
// 都在此点开始运行, 也会有如下错误
// database is locked
MYLOG_D("press enter key to continue\n");
getchar();
return 0;
}
bool is_file_exist(const char* psz_file_path_name, int& i_err_code, std::string& str_err_msg)
{
bool b_rc = false;
int i_rc = -1;
do {
if ((NULL == psz_file_path_name)
|| (0 == strlen(psz_file_path_name))) {
i_err_code = -1;
str_err_msg = "param 1 error";
break;
}
i_rc = access(psz_file_path_name, F_OK);
if (0 != i_rc) {
i_err_code = errno;
str_err_msg = strerror(i_err_code);
} else {
b_rc = true;
}
} while (0);
return b_rc;
}
// @file const_define.h
#if not defined(__CONST_DEFINE_H__)
#define __CONST_DEFINE_H__
#include <string.h>
#include <string>
#include <list>
#ifndef SAFE_DELETE
#define SAFE_DELETE(p) \
if (NULL != (p)) { \
delete (p); \
(p) = NULL; \
}
#endif // #ifndef SAFE_DELETE
#ifndef SAFE_DELETE_ARY
#define SAFE_DELETE_ARY(p) \
if (NULL != (p)) { \
delete[] (p); \
(p) = NULL; \
}
#endif // #ifndef SAFE_DELETE
#define TITLE_LINE80 "================================================================================"
#define LINE80 "--------------------------------------------------------------------------------"
#if not defined(MYLOG_D)
#define MYLOG_D printf
#endif // #if not defined(MYLOG_D)
#define SQLITE_OBJ_DATABASE_PATH_NAME "/var/log/my_sqlite_test_2.db"
#define SQLITE_OBJ_TABLE_NAME "tbl_test_3"
#define FLAG_FILE_TO_STOP_READ "/var/log/flag_stop_read"
#define FLAG_FILE_TO_STOP_WRITE "/var/log/flag_stop_write"
#define MAX_SQL_LENGTH (1024 * 8)
#define MAX_MSG_LENGTH (1024 * 4)
typedef struct _tag_col_name_and_value {
int i_index;
std::string str_name;
std::string str_value;
_tag_col_name_and_value()
{
clear();
}
void clear()
{
i_index = 0;
{
std::string str;
str_name.swap(str);
}
{
std::string str;
str_value.swap(str);
}
}
void set(int i_index, const char* name, const char* value)
{
clear();
this->i_index = i_index;
str_name = ((NULL != name) ? name : "");
str_value = ((NULL != value) ? value : "");
}
}TAG_COL_NAME_AND_VALUE;
typedef struct _tag_sqlite3_callback_info {
// 出参
std::list<TAG_COL_NAME_AND_VALUE> vec_result;
std::list<TAG_COL_NAME_AND_VALUE>::iterator it;
_tag_sqlite3_callback_info()
{
clear();
}
void clear()
{
vec_result.clear();
}
TAG_COL_NAME_AND_VALUE pop_first()
{
TAG_COL_NAME_AND_VALUE rc;
for (it = vec_result.begin(); it != vec_result.end(); it++) {
rc = *it;
vec_result.pop_front();
break;
}
return rc;
}
void show_result() {
TAG_COL_NAME_AND_VALUE rc;
MYLOG_D("%s\n", TITLE_LINE80);
MYLOG_D("%s\n", "show_result");
MYLOG_D("%s\n", LINE80);
for (it = vec_result.begin(); it != vec_result.end(); it++) {
rc = *it;
MYLOG_D("[%d] name = [%s], value = [%s]\n",
rc.i_index,
rc.str_name.c_str(),
rc.str_value.c_str());
}
}
}TAG_SQLITE3_CALLBACK_INFO;
#endif // #if not defined(__CONST_DEFINE_H__)
# ==============================================================================
# @file makefile
# ==============================================================================
# @note
# howto build project
# make BIN_NAME="bin_name_by_you_want" rebuild
# makefile code need tab key not sapce key
MY_MAKE_FILE_PATH_NAME = $(MAKEFILE_LIST)
# macro from Makefile command line
# BIN_NAME
# macro to C project
MAKE_FILE_MACRO__BIN_NAME="make_file_macro__bin_name"
# var define on Makefile
BIN = output_not_give_bin_name
IS_BUILD_TYPE_VALID = 0
ifdef BIN_NAME
IS_BUILD_TYPE_VALID = 1
BIN = $(BIN_NAME)
MAKE_FILE_MACRO__BIN_NAME=$(BIN_NAME)
else
IS_BUILD_TYPE_VALID = 0
endif
LINE80 = --------------------------------------------------------------------------------
# CC = g++ -std=c++98
CC = g++
# -Werror is "warning as error"
CFLAGS = -Wall -Werror -g
INC = -I. -I../../doc/sqlite3_was_install/include/
LIBPATH = -L/usr/lib/ -L/usr/local/lib/ -L../../doc/sqlite3_was_install/lib/
ifeq (1, $(IS_BUILD_TYPE_VALID))
LIBS = -lstdc++ -pthread -lsqlite3
else
LIBS =
endif
DEPEND_CODE_DIR = ../common/ \
DEPEND_CODE_SRC = $(shell find $(DEPEND_CODE_DIR) -name '*.cpp')
DEPEND_CODE_OBJ = $(DEPEND_CODE_SRC:.cpp=.o)
ROOT_CODE_SRC = $(shell find ./ -name '*.cpp')
ROOT_CODE_OBJ = $(ROOT_CODE_SRC:.cpp=.o)
SUB_CODE_DIR = ./empty_dir
SUB_CODE_SRC = $(shell find $(SUB_CODE_DIR) -name '*.cpp')
SUB_CODE_OBJ = $(SUB_CODE_SRC:.cpp=.o)
.PHONY: help
help:
clear
@echo "usage:"
@echo
@echo "build project by given bin name"
@echo "make BIN_NAME=\"bin_name_by_you_want\" rebuild"
@echo
.PHONY: clean
clean:
clear
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo
@echo "make clean begin"
@echo $(LINE80)
@echo "@file $(MY_MAKE_FILE_PATH_NAME)"
@echo "IS_BUILD_TYPE_VALID = $(IS_BUILD_TYPE_VALID)"
@echo "BIN = $(BIN)"
@echo $(LINE80)
rm -f $(ROOT_CODE_OBJ) $(DEPEND_CODE_OBJ) $(SUB_CODE_OBJ)
ifeq (1, $(IS_BUILD_TYPE_VALID))
rm -f ./$(BIN)
endif
@echo "make clean over"
.PHONY: all
all:$(BIN)
@echo $(LINE80)
@echo make all
chmod 777 ./$(BIN)
find . -name "$(BIN)"
$(BIN) : $(ROOT_CODE_OBJ) $(DEPEND_CODE_OBJ) $(SUB_CODE_OBJ)
$(CC) $(CFLAGS) -o $@ $^ $(SHLIBS) $(INC) $(LIBPATH) $(LIBS)
.cpp.o:
$(CC) -c $(CFLAGS) -DMAKE_FILE_MACRO__BIN_NAME="\"$(MAKE_FILE_MACRO__BIN_NAME)\"" $^ -o $@ $(INC) $(LIBPATH) $(LIBS)
.PHONY: rebuild
rebuild:
make -f $(MY_MAKE_FILE_PATH_NAME) clean
ifeq (1, $(IS_BUILD_TYPE_VALID))
@echo $(LINE80)
make -f $(MY_MAKE_FILE_PATH_NAME) all
chmod 775 ./$(BIN)
ldd ./$(BIN)
else
@echo $(LINE80)
@echo "error : Makefile command line input error, please see help"
@echo "please run => make help"
@echo $(LINE80)
endif
#!/bin/bash
# ==============================================================================
# @file build_all_project.sh
# ==============================================================================
make BIN_NAME="test_sqlite_thread_safe" rebuild
补充
最近看了SQLite3.44.2, 官方说明中说是可以支持线程安全的, 好像编译时要加个选项.
但是, 官方也说了, 线程是罪恶, 不推荐用线程安全的Sqlite版本.