Sqlite数据库插入优化
title: SQLite数据库插入优化
date: 2022-05-24 15:47:27

SQLite数据库插入优化 #

需求 #

视频取证业务中,由于部分监控录像品牌视频碎块太多,扫描视频过程中每三秒会产生百万级数据需要插入数据库,原有数据插入接口太慢,无法达到要求,导致我们产品的扫描速度很慢,极大影响客户体验。

视频取证产品使用的是SQLite数据库,SQLite 是一种轻量级、基于文件的关系数据库管理系统 (RDBMS),以其简单性、可移植性和独立性而闻名。它适用于需要独立数据库解决方案的嵌入式系统、移动应用程序和小型项目。SQLite的设计初衷是用于单用户或嵌入式应用,专注于轻量级、低资源消耗。因此,当插入数据量较大时,SQLite的性能会受到影响,比不上MySQL等其他数据库。若替换其他数据库完全没有必要。

性能优化过程 #

现有性能 #

以465.76GB镜像文件为例,取最后十次提交日志:

本次提交数量:5897       本次提交耗时:0.3652427      本次提交速率:16145.428779274713        总提交数量:126400613      总提交耗时:4654.896980299996      平均速率:27154.330919661686

本次提交数量:358796       本次提交耗时:12.5037099      本次提交速率:28695.16350503301        总提交数量:126759409      总提交耗时:4667.4006901999965      平均速率:27158.45872546426

 本次提交数量:50902       本次提交耗时:3.0472488      本次提交速率:16704.24810734194        总提交数量:126810311      总提交耗时:4670.447938999996      平均速率:27151.637842076394
 
本次提交数量:5102       本次提交耗时:0.997792      本次提交速率:5113.2901446393635        总提交数量:126815413      总提交耗时:4671.445730999996      平均速率:27146.93058691558

本次提交数量:375604       本次提交耗时:14.2310447      本次提交速率:26393.28369195552        总提交数量:127191017      总提交耗时:4685.676775699996      平均速率:27144.64165766083

 本次提交数量:490755       本次提交耗时:17.804466      本次提交速率:27563.589944230844        总提交数量:127681772      总提交耗时:4703.481241699996      平均速率:27146.227536319784
 
本次提交数量:254451       本次提交耗时:9.9592031      本次提交速率:25549.333359814704        总提交数量:127936223      总提交耗时:4713.440444799996      平均速率:27142.853399398085

本次提交数量:543249       本次提交耗时:19.6061585      本次提交速率:27708.07958121934        总提交数量:128479472      总提交耗时:4733.046603299996      平均速率:27145.194790691683

本次提交数量:424834       本次提交耗时:14.9654391      本次提交速率:28387.673569831975        总提交数量:128904306      总提交耗时:4748.012042399996      平均速率:27149.111006644

本次提交数量:491245       本次提交耗时:18.247528      本次提交速率:26921.180775828925        总提交数量:129395551      总提交耗时:4766.259570399996      平均速率:27148.238380382798

扫描完成!耗时:1h36m29.3569587s

扫描任务总耗时1小时36分钟29秒,最终提交1亿两千万条数据,提交数据总耗时4766秒,平均速率每秒提交两万七千一百多条数据。

申明:由于环境影响,每次测试数据上下有波动。

第一次优化: #

情况分析 #

后台为了适配多种数据结构的适配,采用了统一的接口,并通过反射操作进行数据存储,反射会极大的影响数据处理性能。

func (ds *DataService) SaveData(cid, eid, tid int64, typeName string, datas interface{}) (row int64, err error) {
	v, ok := vmodel.DataType[typeName]
	if !ok {
		err = fmt.Errorf("not exist typename %s", typeName)
		return
	}
	t := reflect.TypeOf(v)
	if t == nil {
		err = fmt.Errorf("invalid vmodel.DataType: %s", typeName)
		return
	}
	result, err := json.Marshal(datas)
	if err != nil {
		return
	}
	dataArray := reflect.New(reflect.PtrTo(reflect.SliceOf(reflect.PtrTo(t))))
	err = json.Unmarshal([]byte(result), dataArray.Interface())
	if err != nil {
		return
	}
	arrayElem := dataArray.Elem().Elem()
	if !arrayElem.IsValid() {
		return
	}

	arrayData := make([]interface{}, arrayElem.Len(), arrayElem.Len())
	for i := 0; i != arrayElem.Len(); i++ {
		e := arrayElem.Index(i)
		if e.CanAddr() {
			arrayData[i] = e.Addr().Interface()
		} else {
			arrayData[i] = e.Interface()
		}
	}
	
	row, err = proxy.Data.SaveData(cid, eid, typeName, arrayElem.Interface())
	if err != nil {
		return
	}
	return
}

优化方案 #

针对大数据量的数据结构,单独适配提交接口,省去反射操作导致的性能影响。

if TypeName == file.RelateBlockType {
	_, err = service.Data.SaveData_RelateBlock(Task.CaseId, Task.EvidenceId, Task.Id, TypeName, Result)
} else {
	_, err = service.Data.SaveData(Task.CaseId, Task.EvidenceId, Task.Id, TypeName, Result)
}

使用事务机制,可以批量插入数据,可以极大的提升写入速度。

func (dp *DataProxy) SaveData_RelateBlock(eid int64, typeName string, relateBlock []*file.RelateBlock) (row int64, err error) {
	engin, _, err := db.GetDataEngine(eid, typeName, true)
	if err != nil {
		return 0, err
	}
	tableName := db.GetDataTableName(eid, typeName)
	const batchSize = 1000
	totalRows := int64(0)
	// 开始事务
	err = engin.Transaction(func(tx *gorm.DB) error {
		for i := 0; i < len(relateBlock); i += batchSize {
			end := i + batchSize
			if end > len(relateBlock) {
				end = len(relateBlock)
			}
			batch := relateBlock[i:end]
			result := tx.Table(tableName).Create(batch) // 使用事务对象插入
			if result.Error != nil {
				return result.Error
			}
			totalRows += result.RowsAffected
		}
		return nil
	})

	if err != nil {
		return totalRows, err
	}
	return totalRows, nil
}

batchSize = 1000,自测最优速度,实测batchSize 最大值在3500-3700之间,当大于这个值时,会报错:too many SQL variables。网上说batchSize等于SQLITE_MAX_VARIABLE_NUMBER,经过测试好像不是,这个问题没有深究。

  • 版本在 2020-05-22 号前的 3.32.0 默认 999
  • 版本在 3.32.0 后的默认 32766

SQLITE_MAX_VARIABLE_NUMBER 限制了 SQL 查询中绑定变量(占位符)的最大数量。它的默认值是 32766,即每个查询最多允许 32766 个绑定变量。

如何查询SQLITE_MAX_VARIABLE_NUMBER值?

sqlite3   (命令行输入)
SQLite version 3.47.0 2024-10-21 16:30:22
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .limits (命令行再输入)
              length 1000000000
          sql_length 1000000000
              column 2000
          expr_depth 1000
     compound_select 500
             vdbe_op 250000000
        function_arg 127
            attached 10
 like_pattern_length 50000
     variable_number 32766   (SQLITE_MAX_VARIABLE_NUMBER)
       trigger_depth 100
      worker_threads 0
sqlite>

性能对比 #

本次提交数量:16071       本次提交耗时:0.1709622      本次提交速率:94003.23580300207        总提交数量:126664811      总提交耗时:1304.5277936      平均速率:97096.29156344254

本次提交数量:452834       本次提交耗时:4.7283291      本次提交速率:95770.40650575697        总提交数量:127117645      总提交耗时:1309.2561227      平均速率:97091.50317957111

本次提交数量:572900       本次提交耗时:5.5622101      本次提交速率:102998.6263913332        总提交数量:127690545      总提交耗时:1314.8183328      平均速率:97116.49268539922

本次提交数量:255012       本次提交耗时:2.7220906      本次提交速率:93682.40719100239        总提交数量:127945557      总提交耗时:1317.5404234      平均速率:97109.39772901089

本次提交数量:260614       本次提交耗时:2.5024996      本次提交速率:104141.47518744858        总提交数量:128206171      总提交耗时:1320.042923      平均速率:97122.7289402316

本次提交数量:239560       本次提交耗时:2.3600754      本次提交速率:101505.23157014391        总提交数量:128445731      总提交耗时:1322.4029984      平均速率:97130.5503355701

本次提交数量:288596       本次提交耗时:2.9334688      本次提交速率:98380.45661164011        总提交数量:128734327      总提交耗时:1325.3364671999998      平均速率:97133.31684894577

本次提交数量:568353       本次提交耗时:5.5328899      本次提交速率:102722.62963338563        总提交数量:129302680      总提交耗时:1330.8693571      平均速率:97156.5535791988

本次提交数量:9738       本次提交耗时:0.1054929      本次提交速率:92309.52983565719        总提交数量:129312418      总提交耗时:1330.9748499999998      平均速率:97156.16940470364

本次提交数量:83133       本次提交耗时:1.0249508      本次提交速率:81109.25909809524        总提交数量:129395551      总提交耗时:1331.9998007999998      平均速率:97143.82158487184

扫描完成!耗时:29m13.093422s

可以看到扫描任务总耗时已经缩短到29分钟13秒,最终提交1亿两千万条数据,提交数据总耗时1331秒,平均速率每秒97143条数据。插入性能已经极大提生。

第二次优化: #

情况分析 #

因为业务需求,每次刚刚写入数据库的数据,会立马被读取,去进行缩略图提取的任务,而sqlite默认在读的时候会对库加锁,不让继续写。因此读文件导致的锁竞争也会整体影响写数据的速度。

优化方案 #

WAL(Write-Ahead Logging)模式是SQLite3最新的日志模式,也是目前性能最佳的选择。WAL模式对并发读写访问提供了更好的支持,并减少了IO操作的次数,因此能够提高性能和响应时间。

在WAL模式下,日志记录不再是直接写入数据库文件,而是写入一个称为写入日志(write-ahead-log)的文件。在写入日志文件之前,事务对数据库的修改会被写入内存中的WAL文件。这意味着数据库的修改操作不再阻塞其他事务的读写操作。

WAL模式通过减少磁盘操作和并发读写访问的能力来提高性能。在WAL模式中,读操作可以立即完成,而不需要等待写操作完成。同时,WAL模式避免了频繁的IO操作,显著提升了写入性能。

在WAL模式下,读操作可以立即完成,而不需要等待写操作完成。当一个事务需要读取数据库时,它会先读取WAL文件,并根据逻辑记录的内容重构数据库的状态。即使同时有其他事务在修改数据库,读操作也不会受到影响。

写操作在WAL模式下也得到了改进。当一个事务执行写操作时,它首先将修改记录写入WAL文件,并将逻辑记录标记为“已提交”。然后,WAL文件的内容会按顺序写入数据库文件。通过这种方式,WAL模式避免了频繁的磁盘IO操作,提高了写入性能。

Gorm开启WAL模式方法:

func (db *DbManager) getSqlDriver(dsn string, WALModel bool) (*gorm.DB, error) {
	gormDb, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
	if err != nil {
		return nil, err
	}

	if WALModel {
		gormDb.Exec("PRAGMA journal_mode=WAL;")
	}
	return gormDb, err
}

性能对比 #

本次提交数量:63541       本次提交耗时:0.7611553      本次提交速率:83479.6788513461        总提交数量:126338735      总提交耗时:1174.5417230999994      平均速率:107564.28019138459

本次提交数量:207272       本次提交耗时:1.847883      本次提交速率:112167.27465970519        总提交数量:126546007      总提交耗时:1176.3896060999994      平均速率:107571.51061503252

本次提交数量:213402       本次提交耗时:1.9882569      本次提交速率:107331.20051035658        总提交数量:126759409      总提交耗时:1178.3778629999993      平均速率:107571.10514388548

本次提交数量:302122       本次提交耗时:2.6607481      本次提交速率:113547.76500639049        总提交数量:127061531      总提交耗时:1181.0386110999993      平均速率:107584.5698911207

本次提交数量:369731       本次提交耗时:3.4887755      本次提交速率:105977.29776536209        总提交数量:127431262      总提交耗时:1184.5273865999993      平均速率:107579.83601018421

本次提交数量:819281       本次提交耗时:7.422267      本次提交速率:110381.50473433522        总提交数量:128250543      总提交耗时:1191.9496535999992      平均速率:107597.28199311931

本次提交数量:7839       本次提交耗时:0.2082594      本次提交速率:37640.557881180874        总提交数量:128258382      总提交耗时:1192.1579129999993      平均速率:107585.06117469362

本次提交数量:436286       本次提交耗时:4.1007606      本次提交速率:106391.4825947167        总提交数量:128694668      总提交耗时:1196.2586735999994      平均速率:107580.96960142288

本次提交数量:430331       本次提交耗时:3.7038419      本次提交速率:116185.03478779696        总提交数量:129124999      总提交耗时:1199.9625154999994      平均速率:107607.52717862716

本次提交数量:270552       本次提交耗时:2.7364728      本次提交速率:98868.87967605598        总提交数量:129395551      总提交耗时:1202.6989882999994      平均速率:107587.64433892063

扫描完成!耗时:27m12.9716868s

可以看到扫描任务总耗时已经缩短到27分钟12秒,提交数据总耗时1202秒,平均速率每秒107587条数据。整体提升效果虽然不大,但由于并发读原因,缩略图任务从4小时12分钟,已缩短至 3小时46分分钟。

第三次优化: #

情况分析 #

数据提交是在插件通过http请求传输到后台,再进行存储。因此文件传输也会影响插入速度。

优化方案 #

由于是单机版产品,可以在插件中直接提交数据,减少http数据传输导致的速度太慢问题。

性能对比 #

本次提交数量:265830       本次提交耗时:1.727355      本次提交速率:153894.2487213109        总提交数量:126541024      总提交耗时:812.0033392999987      平均速率:155838.05863296922

本次提交数量:238051       本次提交耗时:1.6474042      本次提交速率:144500.66352871992        总提交数量:126779075      总提交耗时:813.6507434999987      平均速率:155815.10373191247

本次提交数量:411942       本次提交耗时:2.5760617      本次提交速率:159911.54249139296        总提交数量:127191017      总提交耗时:816.2268051999987      平均速率:155828.03234308705

本次提交数量:447637       本次提交耗时:2.9583972      本次提交速率:151310.6488878505        总提交数量:127638654      总提交耗时:819.1852023999987      平均速率:155811.71830991586

本次提交数量:9980       本次提交耗时:0.0691937      本次提交速率:144232.78419856145        总提交数量:127648634      总提交耗时:819.2543960999988      平均速率:155810.7403605792

本次提交数量:754626       本次提交耗时:5.0771531      本次提交速率:148631.72040252242        总提交数量:128403260      总提交耗时:824.3315491999988      平均速率:155766.52394854158

本次提交数量:9235       本次提交耗时:0.0687567      本次提交速率:134314.1831996009        总提交数量:128412495      总提交耗时:824.4003058999988      平均速率:155764.73477870916

本次提交数量:291033       本次提交耗时:2.0696496      本次提交速率:140619.45558320597        总提交数量:128703528      总提交耗时:826.4699554999988      平均速率:155726.8079057233

本次提交数量:288612       本次提交耗时:1.9096872      本次提交速率:151130.50975049735        总提交数量:128992140      总提交耗时:828.3796426999988      平均速率:155716.21192858677

本次提交数量:403411       本次提交耗时:2.2078511      本次提交速率:182716.579030171        总提交数量:129395551      总提交耗时:830.5874937999988      平均速率:155787.9837655704

扫描完成!耗时:20m38.326593s

扫描时间已经缩短到20分钟38秒,提交数据总耗时830秒,平均速率每秒155787条数据。

第四次优化: #

情况分析 #

在SQLite中,数据库配置的参数都由编译指示(pragma)来实现的,而其中synchronous选项有三种可选状态,分别是full、normal、off。简要说来,full写入速度最慢,但保证数据是安全的,不受断电、系统崩溃等影响,而off可以加速数据库的一些操作,但如果系统崩溃或断电,则数据库可能会损毁。

优化方案 #

SQLite3中,该选项的默认值就是full,如果我们再插入数据前将其改为off,则会提高效率,既禁用写同步。

需要注意,打开写同步会导致系统崩溃或断电的情况下导致数据丢失,目前对于我们产品来说问题不大,但取消任务也可能会导致数据丢失,但目前没有考虑。

func (db *DbManager) getSqlDriver(dsn string, WALModel bool) (*gorm.DB, error) {
	gormDb, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
	if err != nil {
		return nil, err
	}

	if WALModel {
		gormDb.Exec("PRAGMA journal_mode=WAL;")
		gormDb.Exec("PRAGMA synchronous = OFF;") // 设置 PRAGMA synchronous 为 OFF,禁用写同步
	}
	return gormDb, err
}

性能对比 #

本次提交数量:320239       本次提交耗时:1.5155745999999999      本次提交速率:211298.7377856557        总提交数量:126338735      总提交耗时:641.0061334000002      平均速率:197094.42455703023

本次提交数量:304766       本次提交耗时:1.4957225      本次提交速率:203758.38432596956        总提交数量:126643501      总提交耗时:642.5018559000002      平均速率:197109.93802905208

本次提交数量:326637       本次提交耗时:1.6357978      本次提交速率:199680.54731458865        总提交数量:126970138      总提交耗时:644.1376537000002      平均速率:197116.4661321521

本次提交数量:668516       本次提交耗时:3.3291445      本次提交速率:200807.14429788193        总提交数量:127638654      总提交耗时:647.4667982000002      平均速率:197135.44285953158

本次提交数量:9980       本次提交耗时:0.0449063      本次提交速率:222240.5319520869        总提交数量:127648634      总提交耗时:647.5117045000002      平均速率:197137.18395031724

本次提交数量:640272       本次提交耗时:3.3323363      本次提交速率:192139.0707174423        总提交数量:128288906      总提交耗时:650.8440408000001      平均速率:197111.59349682406

本次提交数量:63170       本次提交耗时:0.3055877      本次提交速率:206716.4352491936        总提交数量:128352076      总提交耗时:651.1496285000002      平均速率:197116.10109595564

本次提交数量:470002       本次提交耗时:2.395864      本次提交速率:196172.23682145565        总提交数量:128822078      总提交耗时:653.5454925000001      平均速率:197112.64093830466

本次提交数量:302921       本次提交耗时:1.5790298      本次提交速率:191839.9513422736        总提交数量:129124999      总提交耗时:655.1245223000001      平均速率:197099.93231007463

本次提交数量:270552       本次提交耗时:1.4943262      本次提交速率:181052.83839632873        总提交数量:129395551      总提交耗时:656.6188485000001      平均速率:197063.41250421776

扫描完成!耗时:18m3.6127236s

扫描任务总耗时18分03秒,提交数据总耗时656秒,平均提交速率每秒197063条。

第五次优化: #

情况分析 #

数据扫描过程中产生的数据会等待数据完全提交之后,再继续扫描。因此提交数据中间的时间浪费也是导致扫描速度慢的主要原因。

优化方案 #

要保证插件在扫描的时候提交数据,提交数据的时候继续扫描,错开时间,同时还要保证数据提交协程每次只有一个?

var concurrencyCh = make(chan struct{}, 1)
case <-ticker.C: //3秒提交一次数据
		concurrencyCh <- struct{}{}
		relateFiles2 := make([]*file2.RelateBlock, len(relateFiles))
		relateFiles = relateFiles[:0]
		wg.Add(1)
		go func() {
			defer wg.Done()
			s.writeData(relateFiles2)
			<-concurrencyCh
		}()

注意数据拷贝,不然会导致数据错乱

性能对比 #

本次提交数量:411798       本次提交耗时:2.1555518      本次提交速率:191040.64212235587        总提交数量:125191532      总提交耗时:647.2781692999998      平均速率:193412.2575698615

本次提交数量:450177       本次提交耗时:2.28864      本次提交速率:196700.6606543624        总提交数量:125641709      总提交耗时:649.5668092999998      平均速率:193423.84370808097

本次提交数量:444626       本次提交耗时:2.3642943      本次提交速率:188058.6524274918        总提交数量:126086335      总提交耗时:651.9311035999998      平均速率:193404.3862974849

本次提交数量:459672       本次提交耗时:2.3378427      本次提交速率:196622.2962733977        总提交数量:126546007      总提交耗时:654.2689462999998      平均速率:193415.88457718925

本次提交数量:424131       本次提交耗时:2.1475108      本次提交速率:197498.89034318243        总提交数量:126970138      总提交耗时:656.4164570999998      平均速率:193429.24240648208

本次提交数量:810616       本次提交耗时:4.3770795      本次提交速率:185195.63101378444        总提交数量:127780754      总提交耗时:660.7935365999998      平均速率:193374.70317502503

本次提交数量:698718       本次提交耗时:3.6598641      本次提交速率:190913.64621981455        总提交数量:128479472      总提交耗时:664.4534006999999      平均速率:193361.147470458

本次提交数量:466650       本次提交耗时:2.5062204      本次提交速率:186196.7127871116        总提交数量:128946122      总提交耗时:666.9596210999998      平均速率:193334.22582214556

本次提交数量:255661       本次提交耗时:1.4855797      本次提交速率:172095.10873095534        总提交数量:129201783      总提交耗时:668.4452007999998      平均速率:193287.0231477022

本次提交数量:193768       本次提交耗时:1.2214007      本次提交速率:158644.08789023946        总提交数量:129395551      总提交耗时:669.6666014999998      平均速率:193223.83811610774

扫描完成!耗时:13m31.9696606s

扫描任务总耗时13分31秒,提交数据总耗时669秒,平均提交速率每秒193223条。由于环境原因提交耗时和速率稍有下降,但整体扫描时间缩短,符合预期效果。

第六次优化: #

情况分析 #

DEFAULT_PAGE_SIZE=4096 是 SQLite 的一个配置选项,用来设置数据库的页面大小。SQLite 数据库的存储是基于“页面”的,每一页的数据通常包含一个数据库表或索引的多个记录。页面大小决定了每一页所能存储的数据量,影响着数据库的性能和空间效率。

增大页面大小(例如 8192 字节或更大)通常能提高写入操作的效率,因为每次 I/O 操作会处理更多的数据,减少了磁盘寻址的开销。然而,较大的页面可能会导致:

  • 内存占用增大,因为 SQLite 会将更多的数据加载到内存中。
  • 对于非常小的数据库来说,较大的页面可能会导致内存浪费,因为每一页的存储量过大,未完全填充页面会造成空间浪费。
  • 缩短查询的效率。

页面大小最大值为65536,通常为512到32768之间的2次幂。

优化方案 #

实测PRAGMA page_size = 40960对于我的业务速度更快

func (db *DbManager) getSqlDriver(dsn string, WALModel bool) (*gorm.DB, error) {
	gormDb, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
	if err != nil {
		return nil, err
	}

	if WALModel {
		gormDb.Exec("PRAGMA page_size = 32768;")
		gormDb.Exec("PRAGMA journal_mode=WAL;")
		gormDb.Exec("PRAGMA synchronous = OFF;") // 设置 PRAGMA synchronous 为 OFF,禁用写同步
		gormDb.Exec("VACUUM;")  //设置页面大小后要执行VACUUM命令
	}

	return gormDb, err
}

当然还有其他配置项可进行调整优化,具体获取方式如下:

func Test_SQLite(t *testing.T) {
	// 连接 SQLite 数据库

	db, err := sql.Open("sqlite3", "your_database.db")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
	_, err = db.Exec("PRAGMA journal_mode=WAL;")
	if err != nil {
		log.Fatal(err)
	}
	// 获取 SQLite 引擎的版本
	var version string
	row := db.QueryRow("SELECT sqlite_version();")
	if err := row.Scan(&version); err != nil {
		log.Fatal(err)
	}

	// 打印版本信息
	fmt.Println("SQLite version:", version)

	var limit int
	err = db.QueryRow("SELECT sqlite_limit(9, -1)").Scan(&limit)
	if err != nil {
		log.Printf("Error getting limit: %v", err)
	}
	fmt.Printf("SQLITE_MAX_VARIABLE_NUMBER = %d\n", limit)

	// 执行 PRAGMA compile_options 语句
	rows, err := db.Query("PRAGMA compile_options;")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	// 打印所有选项,查找 SQLITE_MAX_VARIABLE_NUMBER
	var option string
	for rows.Next() {
		err := rows.Scan(&option)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(option)
		if option == "SQLITE_MAX_VARIABLE_NUMBER" {
			fmt.Println("SQLITE_MAX_VARIABLE_NUMBER is set.")
		}
	}

	if err := rows.Err(); err != nil {
		log.Fatal(err)
	}
}
SQLite version: 3.46.1
2024/11/08 20:02:59 Error getting limit: no such function: sqlite_limit
SQLITE_MAX_VARIABLE_NUMBER = 0
ATOMIC_INTRINSICS=1
COMPILER=gcc-7.1.0
DEFAULT_AUTOVACUUM
DEFAULT_CACHE_SIZE=-2000
DEFAULT_FILE_FORMAT=4
DEFAULT_JOURNAL_SIZE_LIMIT=-1
DEFAULT_MMAP_SIZE=0
DEFAULT_PAGE_SIZE=4096
DEFAULT_PCACHE_INITSZ=20
DEFAULT_RECURSIVE_TRIGGERS
DEFAULT_SECTOR_SIZE=4096
DEFAULT_SYNCHRONOUS=2
DEFAULT_WAL_AUTOCHECKPOINT=1000
DEFAULT_WAL_SYNCHRONOUS=1
DEFAULT_WORKER_THREADS=0
DIRECT_OVERFLOW_READ
ENABLE_FTS3
ENABLE_FTS3_PARENTHESIS
ENABLE_RTREE
ENABLE_UPDATE_DELETE_LIMIT
MALLOC_SOFT_LIMIT=1024
MAX_ATTACHED=10
MAX_COLUMN=2000
MAX_COMPOUND_SELECT=500
MAX_DEFAULT_PAGE_SIZE=8192
MAX_EXPR_DEPTH=1000
MAX_FUNCTION_ARG=127
MAX_LENGTH=1000000000
MAX_LIKE_PATTERN_LENGTH=50000
MAX_MMAP_SIZE=0x7fff0000
MAX_PAGE_COUNT=0xfffffffe
MAX_PAGE_SIZE=65536
MAX_SQL_LENGTH=1000000000
MAX_TRIGGER_DEPTH=1000
MAX_VARIABLE_NUMBER=32766
MAX_VDBE_OP=250000000
MAX_WORKER_THREADS=8
MUTEX_W32
OMIT_DEPRECATED
SYSTEM_MALLOC
TEMP_STORE=1
THREADSAFE=1

性能对比 #

默认4096

本次提交数量:434742       本次提交耗时:2.2563143      本次提交速率:192677.94384851435        总提交数量:124924843      总提交耗时:682.1520413999997      平均速率:183133.42981956524

本次提交数量:442735       本次提交耗时:2.3260672      本次提交速率:190336.28951046645        总提交数量:125367578      总提交耗时:684.4781085999997      平均速率:183157.9073528314

本次提交数量:475816       本次提交耗时:2.430444      本次提交速率:195773.28257717518        总提交数量:125843394      总提交耗时:686.9085525999997      平均速率:183202.54351714425

本次提交数量:414459       本次提交耗时:2.2304693      本次提交速率:185816.94892639856        总提交数量:126257853      总提交耗时:689.1390218999996      平均速率:183211.00530905818

本次提交数量:429761       本次提交耗时:2.2469453      本次提交速率:191264.55815368536        总提交数量:126687614      总提交耗时:691.3859671999996      平均速率:183237.1786674586

本次提交数量:542287       本次提交耗时:2.8832281      本次提交速率:188083.28068112265        总提交数量:127229901      总提交耗时:694.2691952999996      平均速率:183257.30402747146

本次提交数量:650415       本次提交耗时:3.4503263      本次提交速率:188508.25790012963        总提交数量:127880316      总提交耗时:697.7195215999997      平均速率:183283.270771537

本次提交数量:692542       本次提交耗时:3.5574756      本次提交速率:194672.3120181063        总提交数量:128572858      总提交耗时:701.2769971999996      平均速率:183341.0457114022

本次提交数量:496583       本次提交耗时:2.6779366      本次提交速率:185434.9352408119        总提交数量:129069441      总提交耗时:703.9549337999996      平均速率:183349.01114091754

本次提交数量:364790       本次提交耗时:1.8958125      本次提交速率:192418.81778920649        总提交数量:129434231      总提交耗时:705.8507462999996      平均速率:183373.3713231608

扫描完成!耗时:14m15.1331508s

PRAGMA page_size = 8192;

本次提交数量:351563       本次提交耗时:1.74048      本次提交速率:201991.97922412207        总提交数量:125408527      总提交耗时:696.8328506000005      平均速率:179969.3095582654

本次提交数量:337177       本次提交耗时:1.6405582      本次提交速率:205525.77775052417        总提交数量:125745704      总提交耗时:698.4734088000005      平均速率:180029.33600011363

本次提交数量:360320       本次提交耗时:1.7418034      本次提交速率:206866.05618062292        总提交数量:126106024      总提交耗时:700.2152122000005      平均速率:180096.09303372388

本次提交数量:358488       本次提交耗时:1.7744909      本次提交速率:202023.01403743462        总提交数量:126464512      总提交耗时:701.9897031000005      平均速率:180151.5199461334

本次提交数量:328199       本次提交耗时:1.7002102      本次提交速率:193034.36716236616        总提交数量:126792711      总提交耗时:703.6899133000005      平均速率:180182.64665099033

本次提交数量:665446       本次提交耗时:3.6357474      本次提交速率:183028.6669530452        总提交数量:127458157      总提交耗时:707.3256607000005      平均速率:180197.27557156884

本次提交数量:607733       本次提交耗时:3.1744789      本次提交速率:191443.38933864076        总提交数量:128065890      总提交耗时:710.5001396000006      平均速率:180247.5226424289

本次提交数量:548586       本次提交耗时:3.0160173      本次提交速率:181890.86647480435        总提交数量:128614476      总提交耗时:713.5161569000006      平均速率:180254.46902112034

本次提交数量:526139       本次提交耗时:3.0017366      本次提交速率:175278.20395700276        总提交数量:129140615      总提交耗时:716.5178935000006      平均速率:180233.6217581144

本次提交数量:293616       本次提交耗时:1.7229108      本次提交速率:170418.57303349656        总提交数量:129434231      总提交耗时:718.2408043000006      平均速率:180210.077490859

扫描完成!耗时:14m36.1238253s

PRAGMA page_size = 32768

本次提交数量:401703       本次提交耗时:2.113701      本次提交速率:190047.2204914508        总提交数量:125283406      总提交耗时:660.7282707      平均速率:189614.1145395067

本次提交数量:415157       本次提交耗时:2.2785035000000002      本次提交速率:182205.99617248776        总提交数量:125698563      总提交耗时:663.0067742000001      平均速率:189588.65563880687

本次提交数量:383705       本次提交耗时:2.041398      本次提交速率:187961.8771057873        总提交数量:126082268      总提交耗时:665.0481722000001      平均速率:189583.66216227005

本次提交数量:457821       本次提交耗时:2.4507947      本次提交速率:186805.12080428444        总提交数量:126540089      总提交耗时:667.4989669      平均速率:189573.46044695427

本次提交数量:371113       本次提交耗时:2.0288619      本次提交速率:182916.83628146403        总提交数量:126911202      总提交耗时:669.5278288000001      平均速率:189553.2889610637

本次提交数量:667636       本次提交耗时:3.8167794      本次提交速率:174921.29621114596        总提交数量:127578838      总提交耗时:673.3446082      平均速率:189470.3491293212

本次提交数量:701927       本次提交耗时:4.0077466      本次提交速率:175142.56016086446        总提交数量:128280765      总提交耗时:677.3523548000001      平均速率:189385.5747174262

本次提交数量:580064       本次提交耗时:3.0862997      本次提交速率:187948.04665276027        总提交数量:128860829      总提交耗时:680.4386545000001      平均速率:189379.054449353

本次提交数量:348194       本次提交耗时:1.8878260999999998      本次提交速率:184441.77670814068        总提交数量:129209023      总提交耗时:682.3264806000001      平均速率:189365.39424115673

本次提交数量:225208       本次提交耗时:1.290852      本次提交速率:174464.61716757613        总提交数量:129434231      总提交耗时:683.6173326      平均速率:189337.2575966193

扫描完成!耗时:13m53.8350117s

PRAGMA page_size = 65536

本次提交数量:408814       本次提交耗时:2.0995702      本次提交速率:194713.18463178797        总提交数量:125408527      总提交耗时:656.7121511000001      平均速率:190964.2250260473

本次提交数量:434867       本次提交耗时:2.2103785      本次提交速率:196738.70334877036        总提交数量:125843394      总提交耗时:658.9225296000002      平均速率:190983.59571404153

本次提交数量:402638       本次提交耗时:2.0581124      本次提交速率:195634.60188083022        总提交数量:126246032      总提交耗时:660.9806420000002      平均速率:190998.07767138808

本次提交数量:396058       本次提交耗时:2.0033899      本次提交速率:197693.91869251212        总提交数量:126642090      总提交耗时:662.9840319000002      平均速率:191018.31100979185

本次提交数量:458338       本次提交耗时:2.4742645      本次提交速率:185242.119425793        总提交数量:127100428      总提交耗时:665.4582964000002      平均速率:190996.8343434721

本次提交数量:679254       本次提交耗时:3.4836216      本次提交速率:194985.01214942517        总提交数量:127779682      总提交耗时:668.9419180000002      平均速率:191017.60341471076

本次提交数量:509680       本次提交耗时:2.763081      本次提交速率:184460.75232684094        总提交数量:128289362      总提交耗时:671.7049990000003      平均速率:190990.63158825762

本次提交数量:452940       本次提交耗时:2.3351085      本次提交速率:193969.57357655972        总提交数量:128742302      总提交耗时:674.0401075000003      平均速率:191000.95167556472

本次提交数量:590108       本次提交耗时:3.0712546      本次提交速率:192139.06916085692        总提交数量:129332410      总提交耗时:677.1113621000003      平均速率:191006.1139705101

本次提交数量:101821       本次提交耗时:0.5229523      本次提交速率:194704.18238910125        总提交数量:129434231      总提交耗时:677.6343144000003      平均速率:191008.96788942176

扫描完成!耗时:13m49.7302785s

分别设置page_size = 4096、8192、32768、65536,测试其速度。可见总耗时有所下降,但幅度不大。由于测试环境影响,本次优化最优解不如第五次优化。若感觉影响不大,可不采用本次优化方式。

优化结果 #

到目前为止,扫描任务总耗时从1小时36分钟29秒已优化到13分31秒,已符合预期效果。数据插入性能已不再是影响扫描速度慢的主要原因,但是数据插入速度还有优化空间。

其他方案尝试: #

方案一: #

情况分析 #

找到一个方法,说在执行插入前,可以先进行执行准备,执行准备相当于将sql语句提前编译,省去每次执行sql语句时候的语法检查等操作,可以极大的优化sql语句的执行效率,其原理有点像 LuaJit 将 Lua 语言成静态机器码,提高运行速度。

优化方案 #

但不知道是不是我使用方式不对,未达到想要的效果,实测速度比之前要慢,可留作参考

func (dp *DataProxy) SaveData_RelateBlock(cid, eid int64, typeName string, relateBlock []*file.RelateBlock) (row int64, err error) {

	engin, _, err := db.GetDataEngine(cid, eid, typeName, true)
	if err != nil {
		return 0, err
	}
	tableName := db.GetDataTableName(eid, typeName)
	const batchSize = 1000
	totalRows := int64(0)
	// 这里加锁  for循环  relateBlock缓存起来去拿
	dp.Mutex.Lock()
	defer dp.Mutex.Unlock()
	// 开始事务
	err = engin.Transaction(func(tx *gorm.DB) error {
		// 使用原生 SQL 通过 db.DB().Prepare 来获取预编译语句
		sqlDB, err := tx.DB()
		if err != nil {
			return err
		}

		// 创建一个预编译的插入语句
		insertSQL := fmt.Sprintf(
			"INSERT INTO %s (cid, eid, tid, nid, pid, `delete`, `index`, start_offset, `length`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
			tableName,
		)
		stmt, err := sqlDB.Prepare(insertSQL) // 获取一个原生的预编译语句
		if err != nil {
			return err
		}
		defer stmt.Close()

		for i := 0; i < len(relateBlock); i += batchSize {
			end := i + batchSize
			if end > len(relateBlock) {
				end = len(relateBlock)
			}

			batch := relateBlock[i:end]
			for _, block := range batch {
				// 执行预编译的插入语句,传入具体的参数
				_, execErr := stmt.Exec(block.Cid, block.Eid, block.Tid, block.Nid, block.Pid, block.Delete, block.Index, block.StartOffset, block.Length)
				if execErr != nil {
					return execErr
				}
			}
			totalRows += int64(len(batch)) // 累加已插入的行数
		}
		return nil
	})

	if err != nil {
		return totalRows, err
	}
	return totalRows, nil
}

方案二: #

情况分析 #

单个数据性能如果已无法提升,可考虑分库操作。SQLite 并没有像某些传统的关系型数据库(如 MySQL 或 PostgreSQL)那样支持严格的表级锁或行级锁。它的锁机制更侧重于整个数据库的锁定。换句话说,通常情况下,SQLite 会对整个数据库加锁,而不是单独对某个表进行加锁。相对来说分库简单。

优化方案 #

由于我们的数据一向采用pid、nid作为查询条件。那么在插入数据库时,可以将pid作为区分条件,当pid为单数时,插入数据库1,但pid为单数时插入数据库2。

查询时也根据单双数进入不同的数据库查询。还有添加索引等其他问题需要考虑,经过实测插入和查询可行,但目前为止性能已达到要求,已经没有必要,可预留作为后续优化方式。