1. 场景

这两天一直被这个sqlit3困扰,起因是项目中需要有这样一个中间,中间件承担着API角色和流量转发的角色,需要接收来自至少300个agent的请求数据,和健康检测的请求。 所以当即想到用go来实现,因为数据教训,不考虑使用pg大型数据库,所以就选择了轻量化的sqlite数据库。程序很快就开发完了。上线,运行几个节点,数据读写都未发生异常,但是当测试数据到达一定量级后,会出现database is locked错误。 查了些资料,大意是sqlite并发读支持不错,但是并发写就不太友好,所以有了此次的实践。

ps: 部分代码来自于chatGPT,不得不说chatGPT太香了。

在 Gorm 中操作 SQLite3数据库时,由于 SQLite3 的写锁机制是针对整个数据库而不是单个表或行,因此高并发读写可能会导致锁库的情况。

2. 如何避免

为了避免锁库问题,可以采用以下几种方法:

  1. 使用 WAL 模式

    使用 SQLite3 的 WAL(Write-Ahead Logging)模式可以显著降低锁库的概率。在 WAL 模式下,读操作不会阻塞写操作,写操作也不会阻塞读操作,因此可以实现高并发的读写操作。

    可以在 Gorm 中使用以下代码开启 WAL 模式:

    import "gorm.io/driver/sqlite"
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    DSN: "mode=wal",
    }) // 上面这种参数设置方式已经不适用新的设置方法如下
    if Inst, err = gorm.Open(sqlite.Open(dsn), gormConfig); err == nil {
    // 启用 WAL 模式
    _ = Inst.Exec("PRAGMA journal_mode=WAL;")
    //_ = Inst.Exec("PRAGMA journal_size_limit=104857600;")
    //_ = Inst.Exec("PRAGMA busy_timeout=999999;")
    }
  2. 合理控制事务范围

    在进行高并发读写操作时,需要注意事务范围的控制,尽可能缩小事务的范围,减少写锁的占用时间。例如,在进行批量写入操作时,可以将每次写入拆分为多个事务,以减少写锁的占用时间。

  3. 使用缓存

    使用缓存可以减少对数据库的读操作,从而减少锁库的概率。可以使用第三方缓存库(如 Redis)来实现缓存功能。

  4. 增加数据库连接数

    增加数据库连接数可以提高数据库的并发处理能力,减少锁库的概率。可以在 Gorm 中使用以下代码来增加数据库连接数:

    import "gorm.io/driver/sqlite"
    
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
    
    sqlDB, dbError := db.DB()
    if dbError != nil {
    return nil, fmt.Errorf("failed to create sqlDB")
    } // SetMaxIdleConns 设置空闲连接池中连接的最大数量
    sqlDB.SetMaxIdleConns(10) // SetMaxOpenConns 设置打开数据库连接的最大数量。
    sqlDB.SetMaxOpenConns(100)'

    需要注意的是,增加连接数也会增加服务器的负载,因此需要根据实际情况进行调整。

    综上所述,通过采用合适的锁机制、事务控制、缓存和连接数设置等措施,可以有效避免 SQLite3 数据库的锁库问题。

3. 完整的代码示例

  1. 示例1:

    下面是一个完整的 Gorm 操作 SQLite3 数据库的代码示例,其中包括开启 WAL 模式、控制事务范围、使用缓存和增加数据库连接数等措施,以避免锁库问题。

    import (
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    "time"
    ) // 定义模型结构体
    type User struct {
    ID uint
    Name string
    Age uint8
    CreatedAt time.Time
    UpdatedAt time.Time
    } // 初始化数据库连接
    func InitDB() (*gorm.DB, error) {
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    // 开启 WAL 模式
    DSN: "mode=wal",
    // 增加最大连接数为 100
    MaxOpenConns: 100,
    })
    if err != nil {
    return nil, err
    }
    // 设置数据库连接池参数
    sqlDB, err := db.DB()
    if err != nil {
    return nil, err
    }
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)
    sqlDB.SetConnMaxLifetime(time.Hour) return db, nil
    } // 定义批量写入函数
    func BatchInsertUsers(db *gorm.DB, users []User) error {
    // 每次写入 1000 条数据
    batchSize := 1000
    batchCount := (len(users) + batchSize - 1) / batchSize
    for i := 0; i < batchCount; i++ {
    start := i * batchSize
    end := (i + 1) * batchSize
    if end > len(users) {
    end = len(users)
    }
    batch := users[start:end]
    // 启用事务
    tx := db.Begin()
    if err := tx.Error; err != nil {
    return err
    }
    if err := tx.Create(&batch).Error; err != nil {
    tx.Rollback()
    return err
    }
    // 提交事务
    if err := tx.Commit().Error; err != nil {
    return err
    }
    }
    return nil
    } // 查询用户信息
    func GetUsers(db *gorm.DB) ([]User, error) {
    var users []User
    // 使用缓存,减少对数据库的读操作
    err := db.Cache(&users).Find(&users).Error
    if err != nil {
    return nil, err
    }
    return users, nil
    } // 示例代码
    func main() {
    // 初始化数据库连接
    db, err := InitDB()
    if err != nil {
    panic(err)
    }
    defer db.Close() // 批量插入数据
    users := []User{}
    for i := 0; i < 100000; i++ {
    user := User{
    Name: "user_" + string(i),
    Age: uint8(i % 100),
    CreatedAt: time.Now(),
    UpdatedAt: time.Now(),
    }
    users = append(users, user)
    }
    err = BatchInsertUsers(db, users)
    if err != nil {
    panic(err)
    } // 查询数据
    users, err = GetUsers(db)
    if err != nil {
    panic(err)
    }
    for _, user := range users {
    fmt.Println(user)
    }
    }
  2. 示例2:使用 WAL 模式和事务控制来避免锁库问题:

    package main
    
    import (
    "fmt"
    "gorm.io/driver/sqlite"
    "gorm.io/gorm"
    ) type User struct {
    ID uint
    Name string
    } func main() {
    // 创建 SQLite3 数据库连接
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
    // 开启 WAL 模式
    DSN: "mode=wal",
    })
    if err != nil {
    panic("failed to connect database")
    }
    // 设置连接池大小
    sqlDB, err := db.DB()
    if err != nil {
    panic("failed to set database pool size")
    }
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100) // 自动迁移 User 模型对应的表
    err = db.AutoMigrate(&User{})
    if err != nil {
    panic("failed to migrate table")
    } // 并发写入 1000 条数据
    for i := 0; i < 1000; i++ {
    go func(i int) {
    err := db.Transaction(func(tx *gorm.DB) error {
    user := User{Name: fmt.Sprintf("user_%d", i)}
    result := tx.Create(&user)
    return result.Error
    })
    if err != nil {
    fmt.Printf("failed to write data: %v\n", err)
    }
    }(i)
    } // 并发读取数据
    for i := 0; i < 1000; i++ {
    go func() {
    var users []User
    err := db.Transaction(func(tx *gorm.DB) error {
    result := tx.Find(&users)
    return result.Error
    })
    if err != nil {
    fmt.Printf("failed to read data: %v\n", err)
    } else {
    fmt.Printf("read %d records\n", len(users))
    }
    }()
    } // 等待 10 秒钟,以便所有的写入和读取操作都完成
    time.Sleep(10 * time.Second)
    }

    在这个代码示例中,我们首先使用 gorm.Open 函数创建了一个 SQLite3 数据库连接,并设置了连接池大小和 WAL 模式。然后,我们使用 d b.AutoMigrate 函数自动迁移 User 模型对应的表。

    接着,我们在循环中并发地写入 1000 条数据,并使用事务控制来控制事务的范围。每个写入操作都会创建一个 User 对象,并使用 tx.Create 函数将其写入数据库。

    然后,我们在另一个循环中并发地读取数据,并使用事务控制来控制事务的范围。每个读取操作都会使用 tx.Find 函数从数据库中读取所有的 User 记录,并打印出读取的记录数。

    最后,我们等待 10 秒钟,以便所有的写入和读取操作都完成。在这个示例中,我们使用了并发的写入和读取

gorm操作sqlite3,高并发读写如何避免锁库?的更多相关文章

  1. (转载)java高并发:CAS无锁原理及广泛应用

    java高并发:CAS无锁原理及广泛应用   版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处. 博主博客地址是 http://blog.csdn.net/liubenlong007 ...

  2. sqlite3 解决并发读写冲突的问题

    #include "stdafx.h" #include "sqlite3.h" #include <iostream> #include < ...

  3. Java高并发情况下的锁机制优化

    本文主要讲并行优化的几种方式, 其结构如下: 锁优化 减少锁的持有时间 例如避免给整个方法加锁 1 public synchronized void syncMethod(){ 2 othercode ...

  4. MongoDB大数据高并发读写性能测试报告

    服务器大小: 单节点部署,磁盘1T,内存128G 并发导入规模: 1,多线程并发导入csv文件 2,csv文件分1万.10万.100万.200万行记录4种大小 3,每个csv对应一个collectio ...

  5. PHP 解决对文件操作的高并发问题

    解决方案:     对文件进行加锁时,设置一个超时时间.超时设置为1ms,如果这段时间内没有获得锁,就反复获得,直到获得对文件的操作权为止.如果超市限制已到,就必须马上退出,让出锁让其他进程进行操作. ...

  6. 如何在高并发环境下设计出无锁的数据库操作(Java版本)

    一个在线2k的游戏,每秒钟并发都吓死人.传统的hibernate直接插库基本上是不可行的.我就一步步推导出一个无锁的数据库操作. 1. 并发中如何无锁. 一个很简单的思路,把并发转化成为单线程.Jav ...

  7. php中并发读写文件冲突的解决方案

    在这里提供4种高并发读写文件的方案,各有优点,可以根据自己的情况解决php并发读写文件冲突的问题. 对于日IP不高或者说并发数不是很大的应用,一般不用考虑这些!用一般的文件操作方法完全没有问题.但如果 ...

  8. php中并发读写文件冲突的解决方案(文件锁应用示例)

    PHP(外文名: Hypertext Preprocessor,中文名:“超文本预处理器”)是一种通用开源脚本语言.语法吸收了C语言.Java和Perl的特点,入门门槛较低,易于学习,使用广泛,主要适 ...

  9. 朱晔的互联网架构实践心得S2E6:浅谈高并发架构设计的16招

    朱晔的互联网架构实践心得S2E6:浅谈高并发架构设计的16招 概览 标题中的高并发架构设计是指设计一套比较合适的架构来应对请求.并发量很大的系统,使系统的稳定性.响应时间符合预期并且能在极端的情况下自 ...

  10. java高并发系列 - 第22天:java中底层工具类Unsafe,高手必须要了解

    这是java高并发系列第22篇文章,文章基于jdk1.8环境. 本文主要内容 基本介绍. 通过反射获取Unsafe实例 Unsafe中的CAS操作 Unsafe中原子操作相关方法介绍 Unsafe中线 ...

随机推荐

  1. vue3封装input组件

    使用了2种方法去封装input组件(.vue与.jsx) 代码如下 父组件: <template> <div> <h1>input组件封装</h1> & ...

  2. java 泛型使用

    泛型类 // 简单泛型 class Point<T>{ // 此处可以随便写标识符号,T是type的简称 private T var ; public T getVar(){ return ...

  3. How to Check and Repair EXT4 Filesystem in Linux

    The fsck (stands for File System Consistency Check) is used to check and repair one or more Linux fi ...

  4. MFC中利用CFileDialog选择文件并读取文件所遇到的问题和解决方法

    在用MFC编写一个上位机时,需要实现选择和读取一个二进制文件,本来以为很简单的但是在实现过程中遇到很多问题,所幸都一一解决,这里做一下记录. 首先在实现文件选择,在界面上设置一个按钮,并在点击事件函数 ...

  5. stm32 微秒定延时问题

    problem: 如果想用计时器定时微秒级,不要使能自动重载:代码如下: static uint16_t counter; void Delay_us(uint32_t us){ counter=0x ...

  6. Kafka的启动 并创建topic

    一.进入到kafka的bin目录下,运行 ./kafka-server-start.sh -daemon /app/kafka/config/server.properties 注意:如果是0.9版本 ...

  7. NOI模板复习组——图论部分

    1.最小生成树: kruscal: #include <cstdio> #include <cmath> #include <cstring> #include & ...

  8. CGTime CMTimeRange CMTimeMapping 小结

    CMTime CMTimeRange CMTimeMapping 在使用 AVFoundation 框架处理多媒体资源时,通常会用到一些在 CoreMedia 框架中定义的结构体, 这里对其中描述时间 ...

  9. curl下载远程图片到服务器

    <?php //curl下载远程图片到服务器 方法 function download($url, $path = 'images/'){ $ch = curl_init(); $names = ...

  10. AX2012 循环读取DataSource的记录

    static void LoopFormDataSource(Args _args) { FormDataSource formDataSource; SalesLine salesLineSel, ...