SQLite 性能测试

以下性能测试使用 Golang 1.19 测试,均使用内存数据库,环境如下:

goos: linux
goarch: amd64
cpu: AMD EPYC 7K62 48-Core Processor

测试用表结构如下:

test(
    ID INTEGER PRIMARY KEY,
    ID2 INTEGER UNIQUE,
    Name TEXT,
    Content TEXT,
    Time INTEGER
)

读取

在读取测试中,我们使用主键查找指定Row,并读取其值。

Statement 测试

我们测试了以下读取方法:

  • NotPrepare: 不使用 Statement,直接执行 Query
  • PrepareEveryTime: 每次使用一个新的 Statement
  • Prepared: 复用 Statement
BenchmarkReadNotPrepare-8                      93216         12853 ns/op        1238 B/op         69 allocs/op
BenchmarkReadPrepareEveryTime-8                80820         14450 ns/op        1687 B/op         76 allocs/op
BenchmarkReadPrepared-8                       139676          8453 ns/op        1134 B/op         64 allocs/op

显然,复用 statement 的查询是最快的,这也符合我们的预期。值得注意的是,创建 Statement 也是需要消耗资源的,每次查询都创建一个新的 Statement 会极大的拖慢查询。

详细的Pref报告如下:

Method TotalTime Prepare% Query% Next% CloseRow% Scan% CloseStmt% Prepare Query Next CloseRow Scan CloseStmt
NotPrepare 13606 0 43.41 34.88 13.95 3.88 0 0 5906 4745 1898 527 0
EveryTime 16242 33.82 11.03 34.56 6.62 3.68 3.68 5493 1791 5613 1075 597 597
Prepared 8689 0 25.38 51.54 6.92 9.23 0 0 2205 4478 601 801 0

可以发现,使用 Statement 的 Query 时间明显要比不使用的要短,而其他项目的时间都差不多。同时,每次创建 Statement 消耗的资源也非常惊人。

主键/唯一键查询测试

同时,我们还测试了主键和唯一键查询效率的区别:

  • Prepared: 使用主键查询
  • Unique: 使用唯一键查询
BenchmarkReadPrepared-8                       139676          8453 ns/op        1134 B/op         64 allocs/op
BenchmarkReadUnique-8                         118971          8560 ns/op        1135 B/op         64 allocs/op

可以看出,使用主键大概能比使用唯一键有1%的性能优势,且此结果稳定可复现。

批量查询测试

我们还测试了一次查询一行和一次查询多行的效率区别:

  • Prepared: 一次查询一行
  • BatchX: 一次查询X行,使用 WHERE ID>=0 AND ID < X 子句查询
BenchmarkReadPrepared-8                       139676          8453 ns/op        1134 B/op         64 allocs/op
BenchmarkReadBatch2-8                         193335          5825 ns/op         720 B/op         40 allocs/op
BenchmarkReadBatch5-8                         305668          3882 ns/op         424 B/op         27 allocs/op
BenchmarkReadBatch10-8                        371211          3303 ns/op         324 B/op         22 allocs/op
BenchmarkReadBatch100-8                       367106          2870 ns/op         239 B/op         18 allocs/op
BenchmarkReadBatch1000-8                      453716          2788 ns/op         245 B/op         21 allocs/op

一次查询多行可以有一定的性能提升;当行数较大时,性能提升就不明显了。100行与1000行的两次查询性能差不多,多次实验结果可以近似认为其相等。

详细的Prof报告如下:

Method TotalTime Query% Next% CloseRow% Scan% Query Next CloseRow Scan
Single 8689 25.38 51.54 6.92 9.23 2205 4478 601 801
Batch2 6093 9.7 76.87 0.75 5.22 591 4683 45 318
Batch5 3990 8.13 78.05 0 11.38 324 3114 0 454
Batch10 3474 6.2 77.52 0.78 13.18 215 2693 27 457
Batch100 2918 0 84.76 0 13.33 0 2473 0 388

注意 Query 和 CloseRow 是单行查询的平均时间,实际时间要乘上批次大小。标记为0说明单行时间忽略不计。

可以发现,使用多行查询可以有效的节省Query和Close的时间,但Scan的时间没有统计上的区别。

// TODO: 为什么Next的时间会变小呢

需要注意的地方

查询后的Rows需要关闭,否则会极大地影响性能。同时不用的 Statement 也需要关闭。

插入

Statement / Transation 测试

我们测试了以下读取方法:

  • InsertNotTx: 不使用 Transation,直接执行 Query
  • Insert:使用 Transation,直接执行 Query
  • InsertPrepare: 使用 Statement 和 Transation
BenchmarkInsertNotTx-8        125673          9948 ns/op        1079 B/op         30 allocs/op
BenchmarkInsert-8             147528          8345 ns/op        1156 B/op         33 allocs/op
BenchmarkInsertPrepare-8      269584          4439 ns/op        1054 B/op         27 allocs/op

显然,使用 Transation 可以实现更快的插入速度。在没有使用 Transation时,SQLite 会在每次插入的时候自动添加一个 Transation。重复添加的 Transation 会影响插入速度。
同时,使用 Statement 也能加速。

批量插入测试

BenchmarkInsertPrepare-8      269584          4439 ns/op        1054 B/op         27 allocs/op
BenchmarkInsertBulk2-8        173857          6794 ns/op        1171 B/op         24 allocs/op
BenchmarkInsertBulk5-8        258114          4598 ns/op        1172 B/op         19 allocs/op
BenchmarkInsertBulk10-8       259364          4165 ns/op        1224 B/op         17 allocs/op
BenchmarkInsertBulk20-8       255526          4189 ns/op        1347 B/op         16 allocs/op
BenchmarkInsertBulk50-8       254764          4266 ns/op        1700 B/op         16 allocs/op
BenchmarkInsertBulk100-8      242002          4410 ns/op        2342 B/op         16 allocs/op

批量插入没有显著的性能提升,同时由于没有使用 Statement,在小批量时性能还有一些损失。

更新

我们测试了以下更新方法:

  • InsertNotTx: 不使用 Transation,直接执行 Query
  • Insert:使用 Transation,直接执行 Query
  • InsertPrepare: 使用 Statement 和 Transation
BenchmarkUpdateNotTx-8        155088          8351 ns/op         867 B/op         27 allocs/op
BenchmarkUpdate-8             170251          7140 ns/op         986 B/op         30 allocs/op
BenchmarkUpdatePrepare-8      308779          3685 ns/op         895 B/op         24 allocs/op

结论同插入。更新的速度相对于插入要快一些。

分类: 编程

0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注