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 条评论