SQLite 线程安全和并发

SQLite 与线程

SQLite 是线程安全的。

线程模型

SQLite 支持如下三种线程模型

  • 单线程模型 这种模型下,所有互斥锁都被禁用,同一时间只能由一个线程访问。
  • 多线程模型 这种模型下,一个连接在同一时间内只有一个线程使用就是安全的。
  • 串行模型 开启所有锁,可以随意访问。

设置线程模型

SQLite 可以通过以下三种方式进行线程模型的设置,在实际应用中选择任一一项都可以。

  • 编译期设定 通过 SQLITE_THREADSAFE 这个参数进行编译器的设定来选择线程模型
  • 初始化设定 通过调用 sqlite3_config() 可以在 SQLite 初始化时进行设定
  • 运行时设定 通过调用 sqlite3_open_v2() 接口指定数据库连接的数据库模型

SQLite 并发和事务

事务

事务是 SQLite 的核心概念。对数据库的操作 (绝大部分) 会被打包成一个事务进行提交,需要注意的是,这里的打包成事务是自动开启的。举例而言,如果简单在一个 for 循环语句里向数据库中插入 10 条数据,意味着将自动生成 10 个事务。但需要注意的是事务是非常耗时的,一般而言, SQLite 每秒能够轻松支持 50000 条的数据插入,但是每秒仅能够支持几十个事务。一般而言,事务速度受限于磁盘速度。所以在批量插入时需要考虑禁用自动提交,将其用 BEGIN ... COMMIT 打包成一个事务。

回滚模式和 WAL

为了保证写入正确,SQLite 在使用事务进行数据库改写时将拷贝当前数据库文件的备份,即 rollback journal,当事务失败或者发生意外需要回滚时则将备份文件内容还原到数据库中,并同时删除该日志。这是默认的 DELETE 模式。

而后 SQLite 也引入了 WAL 模式,即 Write-Ahead Log。在这种模式下,所有的修改会写入一个单独的 WAL 文件内。这种模式下,写操作甚至可以不去操作数据库,这使得所有的读操作可以在 "写的同时" 直接对数据库文件进行操作,得到更好的并发性能。

锁和并发

SQLite 通过五种锁状态来完成事务。

  • UNLOCKED ,无锁状态。数据库文件没有被加锁。
  • SHARED 共享状态。数据库文件被加了共享锁。可以多线程执行读操作,但不能进行写操作。
  • RESERVED 保留状态。数据库文件被加保留锁。表示数据库将要进行写操作。
  • PENDING 未决状态。表示即将写入数据库,正在等待其他读线程释放 SHARED 锁。一旦某个线程持有 PENDING 锁,其他线程就不能获取 SHARED 锁。这样一来,只要等所有读线程完成,释放 SHARED 锁后,它就可以进入 EXCLUSIVE 状态了。
  • EXCLUSIVE 独占锁。表示它可以写入数据库了。进入这个状态后,其他任何线程都不能访问数据库文件。因此为了并发性,它的持有时间越短越好。

一个线程只有拥有低级别锁时才能够获得更高一级的锁

/*

** Lock the file with the lock specified by parameter eFileLock - one

** of the following:

**

**     (1) SHARED_LOCK

**     (2) RESERVED_LOCK

**     (3) PENDING_LOCK

**     (4) EXCLUSIVE_LOCK

**

** Sometimes when requesting one lock state, additional lock states

** are inserted in between.  The locking might fail on one of the later

** transitions leaving the lock state different from what it started but

** still short of its goal.  The following chart shows the allowed

** transitions and the inserted intermediate states:

**

**    UNLOCKED -> SHARED

**    SHARED -> RESERVED

**    SHARED -> (PENDING) -> EXCLUSIVE

**    RESERVED -> (PENDING) -> EXCLUSIVE

**    PENDING -> EXCLUSIVE

**

** This routine will only increase a lock.  Use the sqlite3OsUnlock()

** routine to lower a locking level.

*/

总结

综上所述,要保证数据库使用的安全,一般可以采用如下几种模式

  • SQLite 采用单线程模型,用专门的线程/队列(同时只能有一个任务执行访问) 进行访问
  • SQLite 采用多线程模型,每个线程都使用各自的数据库连接 (即 sqlite3 *)
  • SQLite 采用串行模型,所有线程都公用同一个数据库连接。

因为写操作的并发性并不好,当多线程进行访问时实际上仍旧需要互相等待,而读操作所需要的 SHARED 锁是可以共享的,所以为了保证最高的并发性,推荐

  • 使用多线程模式
  • 使用 WAL 模式
  • 单线程写,多线程读 (各线程都持有自己对应的数据库连接)
  • 避免长时间事务
  • 缓存 sqlite3_prepare 编译结果
  • 多语句通过 BEGIN 和 COMMIT 做显示事务,减少多次的自动事务消耗

参考

SQLite 是否是线程安全的

Using SQLite In Multi-Threaded Applications

SQLite在多线程环境下的应用

SQLite 插入太慢

SQLite 自动提交

YapDatabase wiki 详解

https://blog.csdn.net/u011342466/article/details/79740086

原文地址:https://www.cnblogs.com/feng9exe/p/10682567.html

时间: 2024-10-10 15:42:10

SQLite 线程安全和并发的相关文章

Java线程测试高并发

package com.expai.utils; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.HttpURLConnection; import java.net.URL; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.Execu

Java线程同步和并发第1部分

通过优锐课核心java学习笔记中,我们可以看到,码了很多专业的相关知识, 分享给大家参考学习.我们将分两部分介绍Java中的线程同步,以更好地理解Java的内存模型. 介绍 Java线程同步和并发是复杂应用程序各个设计阶段中讨论最多的主题. 线程,同步技术有很多方面,它们可以在应用程序中实现高并发性. 多年来,CPU(多核处理器,寄存器,高速缓存存储器和主内存(RAM))的发展已导致通常是开发人员往往忽略的某些领域-例如线程上下文,上下文切换,变量可见性,JVM内存 型号与CPU内存型号. 在本

那些年读过的书《Java并发编程实战》一、构建线程安全类和并发应用程序的基础

1.线程安全的本质和线程安全的定义 (1)线程安全的本质 并发环境中,当多个线程同时操作对象状态时,如果没有统一的状态访问同步或者协同机制,不同的线程调度方式和不同的线程执行次序就会产生不同的不正确的结果.要确保获得最后正确的结果就需要对线程访问对象状态 的操作上进行同步或者协同,使多个线程无论在什么样的调度方式和线程执行顺序的情况中,都能产生正确的结果. 线程安全的本质就对(对象)状态的访问操作进行统一管理,使之在不同的执行环境下均能产生正确的结果.也就是在不同的并发环境下,保持对象状态的不变

第二章:线程安全性——java并发编程实战

一个对象是否需要是线程安全的取决于它是否被多个线程访问. 当多个线程访问同一个可变状态量时如果没有使用正确的同步规则,就有可能出错.解决办法: 不在线程之间共享该变量 将状态变量修改为不可变的 在访问状态变量时使用同步机制 完全由线程安全类构造的程序也不一定是线程安全的,线程安全类中也可以包含非线程安全的类 一.什么是线程安全性 线程安全是指多个线程在访问一个类时,如果不需要额外的同步,这个类的行为仍然是正确的.(因为线程安全类中封装了必要的同步代码) 一个无状态的类是线程安全的.无状态类是指不

内存、线程安全与并发

@内存机制引用自 一.java内存机制 java程序在内存中的分配有4种,分别是: 全局数据区:保存static修饰的属性: 全局代码区:保存static修饰的静态方法: 栈内存空间:保存所有的对象名称,这些对象名称指向对象所在的堆内存空间: 堆内存空间:保存对象: 二.java变量的作用域: java变量分为4种: 类变量:也称为全局变量或者静态变量,需要用修饰符static修饰,在类定义后就分配内存空间,对应内存中的全局数据区,无需实例化就可以使用: 对象变量:也称为成员变量,实例化之后才分

如何处理线程池的并发?

[前言]我们从事Android开发以来,都自始自终被灌输着处理耗时的任务时要在非UI线程做.于是我们有了各种处理并发的编程手段,无论是自己用new Thread(Runnable)新起工作线程(Worker thread),还是利用Android提供的API(AsnyTask,CursorLaoder等)都是处理耗时任务的解决方案.但是在一个大型的应用程序中,如果我们需要处理数量很多且频繁的耗时任务时,如果还是采用之前的手段,无疑会带来很多不便:一来频繁创建销毁线程会造成资源(内存和Cpu)的浪

控制每次线程池的并发线程的最大个数

[本人原创],欢迎交流和分形技术,转载请附上如下内容: 作者:itshare [转自]http://www.cnblogs.com/itshare/ 1. 实验目的:       使用线程池的时候,有时候需要考虑服务器的最大线程数目和程序最快执行所有业务逻辑的取舍.并非逻辑线程越多也好,而且新的逻辑线程必须会在线程池的等待队列中等待 ,直到线程池中工作的线程执行完毕,才会有系统线程取出等待队列中的逻辑线程,进行CPU运算. 2.  解决问题:     <a>如果不考虑服务器实际可支持的最大并行

java 线程 (一): 并发简介

这两天在读<java并发编程实战>,好久没写东西了,准备开始除草.^_^ 并发编程由来: 早年的计算机中没有操作系统,在某个时间段内只支持运行一个程序,并且这个程序能访问计算机的所有资源.在这个程序完全执行完后,再执行下一个程序. 引入并发编程的好处: 高效性:计算机的各个部件不用忙等,例如一个程序在使用IO的时候,CPU可以给另外一个程序使用.这样便提高了设备的使用率. 公平性:计算机上运行的程序应该一视同仁,而不是某一个先执行完后,再让另外一个程序开始执行. 便利性:在计算多个任务时,应该

Netty : writeAndFlush的线程安全及并发问题

使用Netty编程时,我们经常会从用户线程,而不是Netty线程池发起write操作,因为我们不能在netty的事件回调中做大量耗时操作.那么问题来了 – 1, writeAndFlush是线程安全的吗? 2, 是否使用了锁,导致并发性能下降呢 我们来看代码 – 在DefaultChannelHandlerContext中 @Override public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { Defa