golang使用sqlite3,开启wal模式,并发读写

news/2025/2/9 6:09:28 标签: golang, sqlite

因为sqlite是基于文件的,所以默认情况下,sqlite是不支持并发读写的,即写操作会阻塞其他操作,同时sqlite也很容易就产生死锁。

但是作为一个使用广泛的离线数据库,从sqlite3.7.0版本开始(SQLite Release 3.7.0 On 2010-07-21),sqlite引入了更常见的WAL机制来解决页面的读写并发问题。但是sqlite的实现特点决定了其并发能力较低。

SELECT sqlite_version();
3.8.8

开启了WAL模式之后,sqlite就会生成三个文件test.db, test.db-shm, test.db-wal。在WAL模式下支持一写多读。

当临时文件的内容达到一定的量,sqlite会进行一次落盘。

PRAGMA wal_autocheckpoint=5000;

pagesize默认设置的是4k,autocheckpoint设置5000,表示5000个page的数据量,会进行一下checkpoint,也就是20M。

查询日志模式:PRAGMA journal_mode;

设置日志模式:PRAGMA journal_mode=WAL;

示例
CREATE TABLE "users" (
"id"  INTEGER,
"name"  TEXT,
"age"  INTEGER,
"created_at"  TEXT,
"updated_at"  TEXT
);

使用Go的gorm来操作sqlite3

package go_sqlite

import (
	"fmt"
	"strconv"
	"sync"

	"time"

	"database/sql"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

var dbfile = "demos/go_sqlite/test.db"

func Run() {
	gormDB, sqlDB, err := InitDB()
	if err != nil {
		panic(err)
	}
	defer sqlDB.Close()

	users := []User{}
	for i := 0; i < 1000; i++ {
		user := User{
			Name:      "user_" + strconv.Itoa(i),
			Age:       uint8(i % 100),
			CreatedAt: time.Now().Unix(),
			UpdatedAt: time.Now().Unix(),
		}
		users = append(users, user)
	}
	err = BatchInsertUsers(gormDB, users)
	if err != nil {
		panic(err)
	}

	users, err = GetUsers(gormDB)
	if err != nil {
		panic(err)
	}

	fmt.Println(len(users))
	fmt.Println(users[0])
}

type User struct {
	ID        uint
	Name      string
	Age       uint8
	CreatedAt int64
	UpdatedAt int64
}

func InitDB() (*gorm.DB, *sql.DB, error) {
	gormDB, err := gorm.Open(sqlite.Open(dbfile), &gorm.Config{})
	if err != nil {
		return nil, nil, err
	}
	sqlDB, _ := gormDB.DB()
	gormDB.Exec("PRAGMA journal_mode=WAL;")
	sqlDB.SetMaxIdleConns(10)
	sqlDB.SetMaxOpenConns(100)
	sqlDB.SetConnMaxLifetime(time.Hour)

	return gormDB, sqlDB, nil
}

func BatchInsertUsers(gormDB *gorm.DB, users []User) error {
	batchSize := 100
	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 := gormDB.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(gormDB *gorm.DB) ([]User, error) {
	var users []User
	err := gormDB.Find(&users).Error
	if err != nil {
		return nil, err
	}
	return users, nil
}

并发测试

var wg sync.WaitGroup

func Run2() {
	gormDB, err := gorm.Open(sqlite.Open(dbfile), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}
	gormDB.Exec("PRAGMA journal_mode=WAL;")
	sqlDB, _ := gormDB.DB()
	sqlDB.SetMaxIdleConns(10)
	sqlDB.SetMaxOpenConns(100)

	wg.Add(2000)

	// 并发写入 1000 条数据
	for i := 0; i < 1000; i++ {
		go func(i int) {
			defer wg.Done()
			err := gormDB.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() {
			defer wg.Done()
			var users []User
			err := gormDB.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))
			}
		}()
	}

	wg.Wait()

	fmt.Println("done")
}

参考

https://mp.weixin.qq.com/s/9Y1EfzM5cups9oklByAW5Q

https://mp.weixin.qq.com/s/4AhMBJaZ4NZqfqcoPduXjg


http://www.niftyadmin.cn/n/5845665.html

相关文章

PrimeFaces面包屑导航组件的实战应用

在Web开发中&#xff0c;面包屑导航是一种非常实用的功能&#xff0c;它可以帮助用户清晰地了解当前页面在网站中的位置&#xff0c;方便用户快速返回到上一级页面。PrimeFaces作为一款强大的JavaServer Faces&#xff08;JSF&#xff09;组件库&#xff0c;提供了简洁易用的&l…

React 与 Next.js

先说说 React 与 Next.js 结合的作用&#xff1a;高效构建高性能与搜索引擎优化&#xff08;SEO&#xff09;的网页 一. React 网站的“积木” React 用于构建网站中的各个组件&#xff0c;像是“积木”一样组成页面元素&#xff08;如按钮、图片、表单等&#xff09;。这些…

算法兵法全略(译文)

目录 始计篇 谋攻篇 军形篇 兵势篇 虚实篇 军争篇 九变篇 行军篇 地形篇 九地篇 火攻篇 用间篇 始计篇 算法&#xff0c;在当今时代&#xff0c;犹如国家关键的战略武器&#xff0c;也是处理各类事务的核心枢纽。算法的世界神秘且变化万千&#xff0c;不够贤能聪慧…

keil 解决 Error: CreateProcess failed, Command: ‘XXX\ARM\ARMCC\bin\fromelf.exe

从同事手里接手的工程&#xff0c;编译报错 *** Error: CreateProcess failed, Command: E:\Application\keil_5\ARM\ARMCC\bin\fromelf.exe --bin -o ..\Indbus.bin ..\OBJ\Indbus.axf 给大家讲一下&#xff0c;就是这条命令的意思是调用E:\Application\keil_5\ARM\ARM…

DeepSeek与ChatGPT对比:技术、应用与未来趋势

在人工智能飞速发展的时代&#xff0c;大语言模型成为了推动技术革新和产业变革的核心力量。DeepSeek和ChatGPT作为其中的典型代表&#xff0c;各自凭借独特的技术架构、训练方式和应用优势&#xff0c;在不同领域展现出卓越的性能。这两款模型在技术原理、应用表现以及未来发展…

2020-12-27 把int类型拆开并放入一个字符型数组当中。

缘由 把int类型拆开并放入一个字符型数组当中。_编程语言-CSDN问答 char a[47]{}; int aa 0, x 0;std::cin >> aa;while (aa)a[x] (aa % 10) 0, aa / 10;std::cout << a << "倒序\n";

软件测试是什么?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、软件测试的定义和意义 软件测试是伴随着软件工程的重要组成部分&#xff0c;是软件质量保证的重要前提。软件测试是为了尽快尽早地发现在软件产品中所存在的…

【AIGC魔童】DeepSeek核心创新技术(二):MLA

【AIGC魔童】DeepSeek核心创新技术&#xff08;二&#xff09;&#xff1a;MLA 1. MLA框架的定义与背景2. MLA框架的技术原理&#xff08;1&#xff09;低秩联合压缩&#xff08;2&#xff09;查询的低秩压缩&#xff08;3&#xff09;旋转位置嵌入&#xff08;RoPE&#xff09…