当前位置: 首页 > news >正文

SQLite 多线程模型实测分析

网上有很多多线程模型的分析,但是讲的都不是很清楚,大多都是直接下结论,并没有数据支撑。所以想着彻底了解下各种多线程模型,并且实际地测一测数据。

线程模式

在讨论线程模式的前,先弄清楚几个SQLITE_API:

  • sqlite3_open:返回一个数据库连接(句柄)。
  • sqlite3_prepare_v2 / sqlite3_bind_* / sqlite3_step:类似 sqlite3_exec,不过支持参数化 SQL,可以重复执行多次。
  • sqlite3_exec:执行 SQL 语句,等价于上面一组 API,将编译,执行进行了封装。

SQLite supports three different threading modes:

  1. Single-thread. In this mode, all mutexes are disabled and SQLite is unsafe to use in more than a single thread at once.
  2. Multi-thread. In this mode, SQLite can be safely used by multiple threads provided that no single database connection is used simultaneously in two or more threads.
  3. Serialized. In serialized mode, SQLite can be safely used by multiple threads with no restriction.

我测下来的结果:( 同时读写 )

模式多线程单句柄多线程多句柄
串行没问题sqlite3_step 会报 SQLITE_BUSY
单线程sqlite3_prepare_v2处崩溃sqlite3_open 会报 SQLITE_ERROR,sqlite3_step 会报 SQLITE_BUSY
多线程sqlite3_prepare_v2处崩溃sqlite3_step 会报 SQLITE_BUSY

多线程多句柄的环境下,报 SQLITE_BUSY 不可避免。

多线程模型

由上面的测试可知,多线程环境下一共可以定 3 种模型:

  1. 单线程模式 + 单句柄 + 串行队列
  2. 多线程模式 + 多句柄
  3. 串行模式 + 单句柄

别的模型就不用考虑了,因为:

  1. 单线程模式下要么崩溃,要么频繁报错,必须用串行队列。既然有了串行队列,也就没必要多句柄来增加建立句柄的时间。
  2. 多线程模式要是用串行队列,那就和上面的模型没有区别了,更因为多线程底层多了几处地方加锁,性能还会有点损耗。而单句柄是会崩溃的,所以只能选择多句柄。
  3. 串行模式选择多句柄反而会 SQLITE_BUSY 浪费时间,所以选择单句柄。

typedef NS_ENUM(NSInteger, LYDatabaseType) {
    LYDatabaseTypeThreadSingle = 1,
    LYDatabaseTypeThreadMulti,
    LYDatabaseTypeThreadSerialized,
};

static int lastReadCount = 0;
static int readCount = 0;
static int lastWriteCount = 0;
static int writeCount = 0;
static const NSInteger kMaxDataCount = 50000;

- (void)viewDidLoad {
    [super viewDidLoad];
    //统计每秒的读写次数
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(count) userInfo:nil repeats:YES];
}

- (void)count {
    int lastRead = lastReadCount;
    int lastWrite = lastWriteCount;
    lastReadCount = readCount;
    lastWriteCount = writeCount;
    NSLog(@"---%d, %d---", lastReadCount - lastRead, lastWriteCount - lastWrite);
}

//单线程模式 + 单句柄 + 串行队列
- (void)singleTest:(NSString *) path {
    LYDatabase *db = [LYDatabase databaseWithPath:path type:LYDatabaseTypeThreadSingle];
    dispatch_queue_t conQueue = dispatch_queue_create("ly.con", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t serQueue = dispatch_queue_create("ly.ser", NULL);

    if ([db open]) {
        for (NSInteger i = 0; i < 10; i++) { //各开10个线程
            dispatch_async(conQueue, ^{
                while (1) {
                    dispatch_sync(serQueue, ^{
                    //只读测试
//                        BOOL readSuccess = [db executeUpdate:@"SELECT value FROM test WHERE id = ?" withArgumentsInArray:@[@(arc4random() % kMaxDataCount + 1)]];
//                        if (readSuccess) {
//                            readCount++;
//                        }
                    //只写测试
                        BOOL writeSuccess = [db executeUpdate:@"UPDATE test SET value = ? WHERE id = ?;" withArgumentsInArray:@[@(arc4random()), @(arc4random() % kMaxDataCount + 1)]];
                        if (writeSuccess) {
                            writeCount++;
                        }
                    });
                }
            });
        }
    }
}

//多线程模式 + 多句柄
- (void)contest:(NSString *) path {
    dispatch_queue_t conQueue = dispatch_queue_create("ly.con", DISPATCH_QUEUE_CONCURRENT);
    
    for (NSInteger i = 0; i < 10; i++) {
        dispatch_async(conQueue, ^{
            LYDatabase *db = [LYDatabase databaseWithPath:path type:LYDatabaseTypeThreadMulti];
            if ([db open]) {
                while (1) {
                     //只读测试
//                    BOOL readSuccess = [db executeUpdate:@"SELECT value FROM test WHERE id = ?" withArgumentsInArray:@[@(arc4random() % kMaxDataCount + 1)]];
//                    if (readSuccess) {
//                        readCount++;
//                    }
                    //只写测试
                    BOOL writeSuccess = [db executeUpdate:@"UPDATE test SET value = ? WHERE id = ?;" withArgumentsInArray:@[@(arc4random()), @(arc4random() % kMaxDataCount + 1)]];
                    if (writeSuccess) {
                        writeCount++;
                    }
                }
            }
        });
    }
}

//串行模式 + 单句柄
- (void)serTest:(NSString *) path {
    dispatch_queue_t conQueue = dispatch_queue_create("ly.con", DISPATCH_QUEUE_CONCURRENT);
    LYDatabase *db = [LYDatabase databaseWithPath:path type:LYDatabaseTypeThreadSerialized];
    
    if ([db open]) {
        for (NSInteger i = 0; i < 10; i++) {
            dispatch_async(conQueue, ^{
                while (1) {
                //只读测试
//                    BOOL readSuccess = [db executeUpdate:@"SELECT value FROM test WHERE id = ?" withArgumentsInArray:@[@(arc4random() % kMaxDataCount + 1)]];
//                    if (readSuccess) {
//                        readCount++;
//                    }
                //只写测试
                    BOOL writeSuccess = [db executeUpdate:@"UPDATE test SET value = ? WHERE id = ?;" withArgumentsInArray:@[@(arc4random()), @(arc4random() % kMaxDataCount + 1)]];
                    if (writeSuccess) {
                        writeCount++;
                    }
                }
            });
        }
    }
}

复制代码

实测数据

实验环境: 2.7 GHz Intel Core i5 / 8 G 内存 / 模拟器iPhoneX db 里的有 test 表,表里有 50000 条数据,每次都开 10 个线程。 如果最终值能稳定下来,取最终稳定的平均值。

不开WAL

模型只读只写读写
单线程模式 + 单句柄 + 串行队列18000160008500 / 8500
多线程模式 + 多句柄43000950025000 / 50-800(不稳定)
串行模式 + 单句柄15500133007800 / 6300

打开WAL

模型只读只写读写
单线程模式 + 单句柄 + 串行队列360003500018000 / 18000
多线程模式 + 多句柄7200020000-35000(不稳定)17000-35000(不稳定) / 2500-3200(不稳定)
串行模式 + 单句柄270002250013800 / 11000
//加上事务,每秒 commit 一次
[db executeStatements:@"begin exclusive transaction"];
        [NSTimer scheduledTimerWithTimeInterval:1 * NSEC_PER_SEC repeats:YES block:^(NSTimer * _Nonnull timer) {
            [db executeStatements:@"commit transaction"];
            [db executeStatements:@"begin exclusive transaction"];
        }];
复制代码

这种行为只对 单线程模式 + 单句柄 + 串行队列 这种模型有巨大的改良:

模型只读只写读写
单线程模式 + 单句柄 + 串行队列700007600035000 / 35000

要是在使用事务的基础上,再打开 WAL,性能还会增加一点,但不是很明显。

想法

WAL 机制的原理是:修改并不直接写入到数据库文件中,而是写入到另外一个称为 WAL 的文件中;如果事务失败,WAL 中的记录会被忽略,撤销修改;如果事务成功,它将在随后的某个时间被写回到数据库文件中,提交修改。 同步 WAL 文件和数据库文件的行为被称为 checkpoint(检查点),它由 SQLite 自动执行,默认是在 WAL 文件积累到 1000 页修改的时候;当然,在适当的时候,也可以手动执行 checkpoint,SQLite 提供了相关的接口。执行 checkpoint 之后,WAL 文件会被清空。 在读的时候,SQLite 将在 WAL 文件中搜索,找到最后一个写入点,记住它,并忽略在此之后的写入点(这保证了读写和读读可以并行执行);随后,它确定所要读的数据所在页是否在 WAL 文件中,如果在,则读 WAL 文件中的数据,如果不在,则直接读数据库文件中的数据。 在写的时候,SQLite 将之写入到 WAL 文件中即可,但是必须保证独占写入,因此写写之间不能并行执行

所以很明显,WAL 机制对于读写都有性能提高。

当数据库连续执行多个写操作时,可以通过事务(transaction)把多个写操作包在一起,一次性写入磁盘以提高性能。

疑问 对于事务优化,原先觉得是针对的批量写,对于只读按理说性能不会有提高,但是测下来只读的性能也大幅度增加,不明白为什么。

结论

虽然表结构很简单,也只是对单表操作,但是还是能说明一些问题:

  1. 多线程 + 多句柄 对于只读操作性能非常高。
  2. 单线程模式 + 单句柄 + 串行队列 要是加上事务,性能可以达到最优。

第三方库

FMDB

FMDB 没有指定线程模式,所以默认是多线程模式。

FMDatabasePool
类似多线程 + 多句柄模型,用了 Pool 防止句柄建的过多。不过官方说如果不是只读的情况,会导致死锁,我觉得并不会导致死锁,只是会频繁地报 DataBase Lock

FMDatabaseQueue
类似单线程模式 + 单句柄 + 串行队列模型,虽然是多线程模式,但其实性质是一样的,改成单线程模式性能应该还能增加一点点

GYDataCenter

最后安利一个GYDataCenter:高性能数据库框架,这是微信读书出的,在 FMDB 的基础上封装了一层,增加了面向对象的操作接口和 cache 层,而且增加了自动批量写入磁盘的功能,性能得到了大幅度增高。

转载于:https://juejin.im/post/5b7d8522e51d4538e5679f5e

相关文章:

  • 小白都能玩的算法day3-计算机的变革
  • Easyui入门视频教程 第04集---Easyui布局
  • git的安装
  • Visual Studio ALM 词汇表
  • mysql判断两个逗号分隔字符串是否有交集
  • 2018 KDD CUP支付宝安全团队Deep X斩获两项大奖
  • JConsole connection failed
  • tarjan进阶
  • ubuntu13启动屏幕亮度0解决方法
  • 数据结构与抽象 Java语言描述 第4版 pdf (内含标签)
  • 文件尾存在EOF吗?
  • shell使用lftp连接ftp和sftp,并可以指定私钥
  • JMeter中的读取json数据---JSON Extractor插件
  • 添加GDataXMLNODE.h和.m的方法
  • Administrator 被禁用
  • [PHP内核探索]PHP中的哈希表
  • CentOS6 编译安装 redis-3.2.3
  • es6--symbol
  • GraphQL学习过程应该是这样的
  • IDEA常用插件整理
  • Java超时控制的实现
  • mongo索引构建
  • oldjun 检测网站的经验
  • Python 反序列化安全问题(二)
  • Rancher如何对接Ceph-RBD块存储
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • WinRAR存在严重的安全漏洞影响5亿用户
  • 观察者模式实现非直接耦合
  • 聊聊hikari连接池的leakDetectionThreshold
  • 前端路由实现-history
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 怎么将电脑中的声音录制成WAV格式
  • NLPIR智能语义技术让大数据挖掘更简单
  • ​2021半年盘点,不想你错过的重磅新书
  • ​水经微图Web1.5.0版即将上线
  • #传输# #传输数据判断#
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (13)Hive调优——动态分区导致的小文件问题
  • (论文阅读32/100)Flowing convnets for human pose estimation in videos
  • (三) prometheus + grafana + alertmanager 配置Redis监控
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (一)Neo4j下载安装以及初次使用
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • (转)项目管理杂谈-我所期望的新人
  • ******IT公司面试题汇总+优秀技术博客汇总
  • .apk 成为历史!
  • .NET Core 2.1路线图
  • .net core 微服务_.NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .NET 药厂业务系统 CPU爆高分析
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .NET处理HTTP请求