引言

首先,sqlite支持多线程,但是是有条件的支持,也就是同一个连接不能在多线程中使用,不同连接才可以在多线程中使用。这个是最宏观的sqlite多线程准则。

另外,sqlite的文件锁是粗颗粒的,也就是以数据库文件为维度加锁,涉及到5种锁状态。5中锁状态可以使用一句话来总结:sqlite在普通情况(非普通情况就是shared-cache+wal模式)下支持并发读取操作,但是不支持并发写入操作,且不支持并发写入读取混合操作。说白了就是只能并发读取sqlite。

再者有了sqlite3.3以后的shared-cache模式+WAL模式,这两种模式使sqlite支持并发写入读取混合操作,也就是写入和读取都可以并发且不影响了。

本文不涉及SHARED-CACHE + WAL模式,只讲sqlite中的多线程及其意义和使用;

sqlite3中的三种线程模式

Single-thread(单线程):值为0,所有的互斥所都被禁止。这种模式在极度要求速度的情况下被建议使用。因为没有加锁,所以在多线程中使用时是不安全的。该模式下性能最好,在性能优先的模式下选择。

Multi-thread(多线程):值为2,在部分地方加锁,部分地方禁止了互斥所。可以在多线程是使用多个连接,但是一个连接同时被多个线程使用时,是不安全的。

Serialized(串行):值为1,所有的互斥所都被开启。这种模式无论是多个连接在多线程中使用,还是单个连接在多线程中使用,最终都被被强制成串行执行,所以是绝对线程安全的,但是性能最差,在安全性要求高的情况下选择。

官方文档:SQLITE_THREADSAFE=<0 or 1 or 2>

sqlite线程模式的设置

编译阶段:通过使用编译指令配置相关的参数。例如iOS中的sqlite3.lib就是被编译之后的库,这个lib中sqlite3的线程模式被配置成了2,也就是串行模式。具体的指令如下:

gcc -DSQLITE_THREADSAFE=0 shell.c sqlite3.c -ldl

其意义是:编译时设置SQLITE_THREADSAFE参数的值为0,编译shell.c和sqlite3.c,生成命令行执行程序。sqlite3编译设置

初始化阶段:在调用sqlite3_initialize()之前使用sqlite3_config()函数设置。因为sqlite3_initialize()一般都被封装在了open方法中,所以这个阶段可以认为是在调用open方法之前使用sqlite3_config()来设置线程模式。

运行时:通过sqlite3_open_v2()中的第三个参数来设置,可选值为SQLITE_OPEN_NOMUTEX(无锁即多线程模式),SQLITE_OPEN_FULLMUTEX(全锁即串行模式)

sqlite3_threadsafe()方法

该方法包含以下几个重点:

  • 该方法返回编译时期所设置的线程模式的值
  • 该方法返回值不受其他阶段重置线程模式的影响,也就是说即使在初始化或者runtime阶段改变了线程模式,该函数的返回值不变。

sqlite3_config方法

#define SQLITE_CONFIG_SINGLETHREAD  1  /* nil */
#define SQLITE_CONFIG_MULTITHREAD   2  /* nil */
#define SQLITE_CONFIG_SERIALIZED    3  /* nil */
#define SQLITE_CONFIG_MALLOC        4  /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_GETMALLOC     5  /* sqlite3_mem_methods* */
#define SQLITE_CONFIG_SCRATCH       6  /* No longer used */
#define SQLITE_CONFIG_PAGECACHE     7  /* void*, int sz, int N */
#define SQLITE_CONFIG_HEAP          8  /* void*, int nByte, int min */
#define SQLITE_CONFIG_MEMSTATUS     9  /* boolean */
#define SQLITE_CONFIG_MUTEX        10  /* sqlite3_mutex_methods* */
#define SQLITE_CONFIG_GETMUTEX     11  /* sqlite3_mutex_methods* */
/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */
#define SQLITE_CONFIG_LOOKASIDE    13  /* int int */
#define SQLITE_CONFIG_PCACHE       14  /* no-op */
#define SQLITE_CONFIG_GETPCACHE    15  /* no-op */
#define SQLITE_CONFIG_LOG          16  /* xFunc, void* */
#define SQLITE_CONFIG_URI          17  /* int */
#define SQLITE_CONFIG_PCACHE2      18  /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_GETPCACHE2   19  /* sqlite3_pcache_methods2* */
#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20  /* int */
#define SQLITE_CONFIG_SQLLOG       21  /* xSqllog, void* */
#define SQLITE_CONFIG_MMAP_SIZE    22  /* sqlite3_int64, sqlite3_int64 */
#define SQLITE_CONFIG_WIN32_HEAPSIZE      23  /* int nByte */
#define SQLITE_CONFIG_PCACHE_HDRSZ        24  /* int *psz */
#define SQLITE_CONFIG_PMASZ               25  /* unsigned int szPma */
#define SQLITE_CONFIG_STMTJRNL_SPILL      26  /* int nByte */
#define SQLITE_CONFIG_SMALL_MALLOC        27  /* boolean */
#define SQLITE_CONFIG_SORTERREF_SIZE      28  /* int nByte */
#define SQLITE_CONFIG_MEMDB_MAXSIZE       29  /* sqlite3_int64 */

各个值得意义:官方文档

几个重点

  • sqlite3_config(int,...)可以设置多个值
  • sqlite3_config不是线程安全的,当该方法在执行时,确保其他线程没有调用该方法
  • 这里的线程模式的值是1、2、3,而编译阶段设置的线程模式的值为0、1、2
Muti-thread模式下的并发

根据官方文档,只要保证了多线程中不同时使用同一个connect即可,所以path使用同一个,也就意味着使用同一个数据库,但是并发中取创建新的db,也就是open的是不同的db,也就是和同一个数据库建立了多个不同的连接,代码如下
主要并发逻辑:

- (void)mutiThreadTest {// iOS中的sqlite3lib默认是2,也就是muti-thread。也就是说应用程序需要自己去保证不再多线程中同时使用同一个数据库连接(database-connection)。也就是说,可以通过建立多个数据库连接来实现并行访问sqlitedispatch_queue_t t = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);dispatch_async(t, ^{FMDatabase *db1 = [FMDatabase databaseWithPath:self.path];[self addDataFrom:0 count:1000 withDB:db1 withFlag:@"1"];});dispatch_async(t, ^{FMDatabase *db2 = [FMDatabase databaseWithPath:self.path];[self addDataFrom:1000 count:1000 withDB:db2 withFlag:@"2"];});dispatch_async(t, ^{FMDatabase *db3 = [FMDatabase databaseWithPath:self.path];[self addDataFrom:2000 count:1000 withDB:db3 withFlag:@"3"];});dispatch_async(t, ^{FMDatabase *db4 = [FMDatabase databaseWithPath:self.path];[self addDataFrom:3000 count:1000 withDB:db4 withFlag:@"4"];});
}

viewDidLoad方法:

- (void)viewDidLoad {[super viewDidLoad];[self.view addSubview:self.tableView];[self create];UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];[btn setTitle:@"查询" forState:UIControlStateNormal];btn.backgroundColor = [UIColor redColor];[btn addTarget:self action:@selector(mutiThreadTest) forControlEvents:UIControlEventTouchUpInside];[self.view addSubview:btn];btn.frame = CGRectMake(100,100, 40, 40);
}

创建数据库方法:

- (void)create {self.path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.db"];self.db = [FMDatabase databaseWithPath:self.path];self.queue = [FMDatabaseQueue databaseQueueWithPath:self.path];if ([self.db open]) {[self.db executeUpdate:@"CREATE table if not exists ClientTable (name text, no text, signature text,PRIMARY KEY(no));"];[self.db executeUpdate:@"delete from ClientTable"];}NSLog(@"%@",self.path);
}

结果:
step方法报错,错误码为5,即SQLITE_BUSY;
所以,sqlite中的线程安全意味着什么呢?

sqlite的多线程模式中的条件的具体意义

sqlite的线程安全意味着数据的安全,如果错误的使用将会导致异常,比如崩溃、数据错乱。

如官方文档中锁描述的,其本质是不要在多线程中同时使用同一个connect或者statement,这里的具体意思:
connect的代表是sqlite3对象,所以这里的维度不是以open-close为维度,而是以sqlite对象当前被哪个线程使用到为标准,也就是说可以在thread1中open,然后再thread2中step,然后在thread3中close,只要这3个步骤不是同时进行的就没有问题。但是如果是同一个sqlite对象同时在两个线程中被调用,哪怕只是sqlite3_errmsg(sqlite);这种函数,也会引发崩溃!

验证代码:

#import "ViewController.h"
#import <sqlite3.h>#define DATABASEPATH [[NSTemporaryDirectory() stringByAppendingPathComponent:@"testSql01.db"] UTF8String]@interface ViewController (){sqlite3 *sqlite;
}@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];UIButton *btnx = [UIButton buttonWithType:UIButtonTypeSystem];[self.view addSubview:btnx];[btnx setTitle:@"多线程打开" forState:UIControlStateNormal];btnx.frame = CGRectMake(0, 200, 80, 20);btnx.backgroundColor = [UIColor redColor];[btnx addTarget:self action:@selector(openUnSafe) forControlEvents:UIControlEventTouchUpInside];UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];[self.view addSubview:btn];[btn setTitle:@"串行打开" forState:UIControlStateNormal];btn.frame = CGRectMake(0, 300, 80, 20);btn.backgroundColor = [UIColor redColor];[btn addTarget:self action:@selector(openSafe) forControlEvents:UIControlEventTouchUpInside];UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeSystem];[self.view addSubview:btn2];[btn2 setTitle:@"创建" forState:UIControlStateNormal];btn2.frame = CGRectMake(100, 300, 80, 20);btn2.backgroundColor = [UIColor redColor];[btn2 addTarget:self action:@selector(createDB) forControlEvents:UIControlEventTouchUpInside];UIButton *btn3 = [UIButton buttonWithType:UIButtonTypeSystem];[self.view addSubview:btn3];[btn3 setTitle:@"并发插入" forState:UIControlStateNormal];btn3.frame = CGRectMake(200, 300, 80, 20);btn3.backgroundColor = [UIColor redColor];[btn3 addTarget:self action:@selector(asynStep) forControlEvents:UIControlEventTouchUpInside];UIButton *btn4 = [UIButton buttonWithType:UIButtonTypeSystem];[self.view addSubview:btn4];[btn4 setTitle:@"关闭" forState:UIControlStateNormal];btn4.frame = CGRectMake(300, 300, 80, 20);btn4.backgroundColor = [UIColor redColor];[btn4 addTarget:self action:@selector(close) forControlEvents:UIControlEventTouchUpInside];
}- (void)asynStep {NSString *name = [NSString stringWithFormat:@"%d",arc4random()/20];dispatch_queue_t queue = dispatch_queue_create([name UTF8String],  DISPATCH_QUEUE_CONCURRENT);// 并发操作dispatch_async(queue, ^{[self testMethod];});dispatch_async(queue, ^{[self testMethod];});
}- (void)openSafe {int openFlage = sqlite3_open_v2(DATABASEPATH, &sqlite, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL);NSLog(@"path:%@",[NSString stringWithUTF8String:DATABASEPATH]);if (openFlage != SQLITE_OK) {sqlite3_close(sqlite);NSLog(@"数据库打开失败!");return;}NSLog(@"数据库打开成功!");}- (void)openUnSafe {int openFlage = sqlite3_open_v2(DATABASEPATH, &sqlite, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, NULL);NSLog(@"path:%@",[NSString stringWithUTF8String:DATABASEPATH]);NSLog(@"%@",[NSThread currentThread]);if (openFlage != SQLITE_OK) {sqlite3_close(sqlite);NSLog(@"数据库打开失败!");return;}NSLog(@"数据库打开成功!");}- (void)createDB {NSLog(@"%@",[NSThread currentThread]);NSString *deleteSql = @"delete from ClientTable";char *err = 0;int deleteFlag = sqlite3_exec(sqlite, [deleteSql UTF8String], NULL, NULL, &err);if (deleteFlag != SQLITE_OK) {NSLog(@"数据库表删除失败!");NSLog(@"%s",err);}NSString *sql = @"CREATE table if not exists ClientTable (name text, no text, signature text,PRIMARY KEY(no));delete from ClientTable";int createFlag = sqlite3_exec(sqlite, [sql UTF8String], NULL, NULL, NULL);if (createFlag != SQLITE_OK) {NSLog(@"数据库表创建失败!");}NSLog(@"数据库表创建成功!");sqlite3_free(err);
}- (BOOL)testMethod {NSLog(@"%@",[NSThread currentThread]);// 这里即使只是调用sqlite3_errmsg方法,如果是多线程中同时使用,也会引起崩溃sqlite3_errmsg(sqlite);return YES;
}- (void)close {if (sqlite3_close(sqlite) == SQLITE_OK) {NSLog(@"数据库关闭成功");}
}@end

结果:

resutl

GIF:

result

解释:
1、第一次点击并未崩溃,这里只是概率事件,因为sqlite3_errmsg执行的速度较快的话可能会不崩溃;

2、演示过程中,数据库在多个线程进行了操作,但是只要不是同时的,就不会崩溃;

sqlite多线程的实现

根据源代码可知,多线程模式主要是由bCoreMutex和bFullMutex来控制,具体原理和实现步骤本文不作深究。


For More essence

iOS数据库的使用(三):sqlite多线程相关推荐

  1. 使用iOS原生sqlite3框架对sqlite数据库进行操作

    摘要: iOS中sqlite3框架可以很好的对sqlite数据库进行支持,通过面向对象的封装,可以更易于开发者使用. 使用iOS原生sqlite3框架对sqlite数据库进行操作 一.引言 sqlit ...

  2. android sqlite使用之模糊查询数据库数据的三种方式

    android sqlite使用之模糊查询数据库数据的三种方式 android应用开发中常常需要记录一下数据,而在查询的时候如何实现模糊查询呢?很少有文章来做这样的介绍,所以这里简单的介绍下三种sql ...

  3. iOS面试题-(三)

    目前形势,参加到iOS队伍的人是越来越多,甚至已经到供过于求了.今年,找过工作人可能会更深刻地体会到今年的就业形势不容乐观,加之,培训机构一火车地向用人单位输送iOS开发人员,打破了生态圈的动态平衡. ...

  4. iOS 数据库-SQLite3 CoreData FMDB

    在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列-Objective-C之Foundation框架的文章中提到归档.plist文件存储,包括偏 ...

  5. ocbase 数据库 蚂蚁_iOS - OC SQLite 数据库存储

    前言 采用 SQLite 数据库来存储数据.SQLite 作为一中小型数据库,应用 iOS 中,跟前三种保存方式相比,相对比较复杂一些. 注意:写入数据库,字符串可以采用 char 方式,而从数据库中 ...

  6. iOS 数据库操作(使用FMDB)

    iOS 数据库操作(使用FMDB)   iOS中原生的SQLite API在使用上相当不友好,在使用时,非常不便.于是,就出现了一系列将SQLite API进行封装的库,例如FMDB.Plausibl ...

  7. IOS数据库操作SQLite3使用详解(转)

    iPhone中支持通过sqlite3来访问iPhone本地的数据库. 具体使用方法如下 1:添加开发包libsqlite3.0.dylib 首先是设置项目文件,在项目中添加iPhone版的sqlite ...

  8. IOS数据库操作SQLite3使用详解

    sqlite数据库iosdatabasesqlinteger 目录(?)[+] iPhone中支持通过sqlite3来访问iPhone本地的数据库. 具体使用方法如下 1:添加开发包libsqlite ...

  9. Android SQLite多线程读写和线程同步源码分析

    没啥诀窍,只需保证几个线程都是用的一个SQLiteDataBase对象就行了. 如果我们非要在不同线程中用两个或更多的SQLiteDataBase对象呢,当然这些SQLiteDataBase对象所操作 ...

  10. android数据库isnull,Android中SQLite数据库知识点总结

    SQLite 数据库简介 SQLite 是一个轻量级数据库,它是D. Richard Hipp建立的公有领域项目,在2000年发布了第一个版本.它的设计目标是嵌入式的,而且占用资源非常低,在内存中只需 ...

最新文章

  1. 正则表达式从基础到深入实战
  2. mysql模糊查询后分页_jsp模糊查询后的数据进行分页,但点击下一页后就查询全部的了...
  3. uni-app——Vue3简单整合uView@1.8.4解决方案
  4. python交通标志识别_YOLOv3目标检测实战:交通标志识别
  5. 我想知道怎么求N的N次方
  6. 安卓手机上运行 PC-E500 程序
  7. HTML标签(持续更新)
  8. 搜狐超越新浪给创业者的两个启示:不断+耐心布局
  9. 通过Chrome扩展来批量复制知乎好友
  10. to load JavaHL Library解决方法
  11. java如何使用live2d_关于live2D的使用
  12. 冰点还原无法修改计算机时间,系统还原后无法更改系统时间?这个方法必须会...
  13. 常用学术文献数据库界面及导出参考文献方法
  14. pip install pyodbc : ERROR: Command errored out with exit status 1
  15. 对 sass和less的理解
  16. python实现小游戏-猜年龄
  17. 玩机搞机---全网最详细的手机全机型 刷机教程 二
  18. R语言使用epiDisplay包的kap函数(kap.m.raters)计算Kappa统计量的值(总一致性、期望一致性)、对多个评分对象的结果进行一致性分析、评分的类别为多个类别
  19. php rand 生成带有小数的随机数
  20. 执行kubectl get csr显示NoT found.

热门文章

  1. java 动态代理 jdk为何比cglib效率低 原理总结
  2. vue+axios上传文件
  3. 技术复习-java类加载机制
  4. vue插入富文本编辑器(支持视频和图片的上传)
  5. 异常的分类以及什么异常触发回滚
  6. Halcon之 Variation Model(转)
  7. [转载]修改SDE权限造成无法在ArcMap中绘制图形的解决办法
  8. erlang随机数问题
  9. windows 下pcl的安装和编译
  10. mysql和php长度的漏洞_mysql和php字符长度判断