8. 统一SQL性能测试报告
8.1. 测试目的
本次测试旨在评估 JAVA 调用统一 SQL 解析和改写的性能表现,测试包括对复杂 SQL 和简单 SQL 的转换进行性能测试,通过单线程基准测试评估统一SQL整个转换流程的性能表现。
8.2. 业务术语定义
JMH :(Java Microbenchmark Harness)是一个用于编写、运行和分析Java基准测试的工具。
JMH基准测试结果中常见的几个参数含义如下:
Mode(模式):表示基准测试的模式,通常有平均时间、吞吐量和样本时间等模式。平均时间模式(Mode.AverageTime)表示每次操作的平均耗时;吞吐量模式(Mode.Throughput)表示每秒钟可以执行的操作数;样本时间模式(Mode.SampleTime)表示每个样本执行的时间。
Cnt(计数):表示执行基准测试的次数。
Score(得分):表示基准测试的结果,通常是操作的平均执行时间或每秒钟可以执行的操作数等。
Error(误差):表示基准测试结果的误差范围。通常使用标准差表示。
8.3. 测试环境
8.3.1. 测试用例
基准测试的测试用例是简单SQL(1kb左右)和复杂SQL(44kb左右)。
对比测试的案例见附件
备注
下载附件后请去掉 .rename
后缀
8.3.2. 测试场景
基准测试
基准测试使用 C 语言调用 TransferSQL 函数来进行性能测试。
对比测试:
8.3.3. 测试工具
8.3.3.1. 基准测试
表 2-1 测试工具
用途 |
工具 |
版本 |
---|---|---|
基准测试 |
GCC |
4.8.5 |
对比测试 |
JMeter |
5.5 |
表 2-2 软硬件配置
环境 |
系统/资源 |
型号/配置/软件名称/软件版本号 |
---|---|---|
硬件环境 |
linux 3.10.0 |
Intel(R) Xeon(R) Gold 6250 CPU @ 3.90GHz |
软件环境 |
sql-convert-runtime-go |
unisql-24.2.0.0-26867 |
8.3.3.2. 对比测试
环境 |
系统/资源 |
型号/配置/软件名称/软件版本号 |
---|---|---|
硬件环境 |
CPU |
Intel(R) Xeon(R) Gold 6250 CPU @ 3.90GHz (限制4C) |
软件环境 |
sql-convert-runtime |
23.1.1.0 |
sql-convert-runtime-go |
unisql-23.1.1.0-11986 |
8.4. 测试结果
8.4.1. 基准测试
8.4.1.1. 测试结果
unisql-performance-test-c.zip.rename
备注
下载附件后请去掉 .rename
后缀
是否缓存 |
SQL大小 |
并发数 |
总时间 (us) |
每个线程运行次数 |
用户体感耗时 (us) |
总吞吐量 (queries/s) |
---|---|---|---|---|---|---|
否 |
856 |
1 |
748537 |
5000 |
149.71 |
6679.69 |
是 |
856 |
1 |
81291729 |
100000000 |
0.81 |
1234567.90 |
是 |
856 |
2 |
48707594 |
50000000 |
0.97 |
2083333.33 |
是 |
856 |
4 |
26240807 |
25000000 |
1.05 |
3846153.84 |
是 |
856 |
8 |
15473441 |
12500000 |
1.24 |
6666666.67 |
是 |
856 |
16 |
7889151 |
6250000 |
1.26 |
14285714.29 |
是 |
856 |
32 |
4882088 |
3125000 |
1.56 |
25000000.00 |
否 |
22426 |
1 |
15667669 |
5000 |
3133.53 |
319.13 |
是 |
22426 |
1 |
241179383 |
10000000 |
24.12 |
41493.77 |
是 |
22426 |
2 |
120316536 |
5000000 |
24.06 |
83333.33 |
是 |
22426 |
4 |
61748721 |
2500000 |
24.70 |
163934.43 |
是 |
22426 |
8 |
30528287 |
1250000 |
24.42 |
333333.33 |
是 |
22426 |
16 |
15970875 |
625000 |
25.55 |
666666.67 |
是 |
22426 |
32 |
10281346 |
310000 |
33.17 |
992000.00 |
8.4.1.2. 测试结论
根据测试结果,在单线程的基准测试场景下,可以得出以下结论:
不走缓存的场景下,简单SQL的平均转换耗时是约149微秒(us),复杂SQL的平均转换耗时是约3133微秒(us)。
缓存的场景下,简单SQL的平均转换耗时降低至约1微秒(us),复杂SQL的平均转换耗时降低至约24微秒(us)。
8.4.2. 对比测试
对比测试的目的是在模仿真实调用的场景下,验证加了统一 SQL 之后,与直连查询的性能对比情况。
8.4.2.1. 测试结果
表 3-2 对比测试结果
Benchmark |
线 程数 |
Median |
95% |
99% |
TPS |
---|---|---|---|---|---|
转 SQL1 无缓存 |
1 |
0 |
1 |
1 |
2189.22 |
转 SQL2 无缓存 |
1 |
1 |
2 |
2 |
799.39 |
转 SQL3 无缓存 |
1 |
20 |
23 |
30 |
47.89 |
转 SQL1 缓存 |
1 |
0 |
1 |
1 |
6792.53 |
转 SQL2 缓存 |
1 |
0 |
1 |
1 |
5355.49 |
转 SQL3 缓存 |
1 |
2 |
3 |
13 |
458.84 |
直 SQL1 |
1 |
0 |
1 |
1 |
2682.71 |
直 SQL2 |
1 |
1 |
1 |
2 |
1066.01 |
直 SQL3 |
1 |
18 |
22 |
29 |
52.03 |
直 SQL1 缓存 |
1 |
0 |
1 |
1 |
6730.69 |
直 SQL2 缓存 |
1 |
0 |
1 |
1 |
5429.59 |
直 SQL3 缓存 |
1 |
2 |
3 |
14 |
422.01 |
转 SQL1 无缓存 |
3 |
1 |
1 |
1 |
5299.47 |
转 SQL2 无缓存 |
3 |
1 |
2 |
2 |
2173.32 |
转 SQL3 无缓存 |
3 |
21 |
31 |
38 |
130.64 |
转 SQL1 缓存 |
3 |
0 |
1 |
1 |
15327.56 |
转 SQL2 缓存 |
3 |
0 |
1 |
1 |
12167.60 |
转 SQL3 缓存 |
3 |
2 |
3 |
13 |
1329.01 |
直 SQL1 |
3 |
0 |
1 |
1 |
6253.94 |
直 SQL2 |
3 |
1 |
1 |
2 |
3027.08 |
直 SQL3 |
3 |
19 |
24 |
30 |
148.44 |
直 SQL1 缓存 |
3 |
0 |
1 |
1 |
15062.11 |
直 SQL2 缓存 |
3 |
0 |
1 |
1 |
12185.33 |
转 SQL1 无缓存 |
20 |
1 |
5 |
11 |
10514.89 |
转 SQL2 无缓存 |
20 |
3 |
9 |
18 |
4839.59 |
转 SQL3 无缓存 |
20 |
36 |
63 |
80 |
510.15 |
转 SQL1 缓存 |
20 |
1 |
1 |
3 |
32707.27 |
转 SQL2 缓存 |
20 |
1 |
1 |
5 |
25815.99 |
转 SQL3 缓存 |
20 |
4 |
8 |
17 |
4757.39 |
直 SQL1 |
20 |
1 |
3 |
6 |
15194.98 |
直 SQL2 |
20 |
2 |
4 |
8 |
9762.83 |
直 SQL3 |
20 |
31 |
48 |
62 |
617.87 |
直 SQL1 缓存 |
20 |
1 |
1 |
3 |
33059.32 |
直 SQL2 缓存 |
20 |
1 |
1 |
5 |
26639.60 |
直 SQL3 缓存 |
20 |
4 |
9 |
17 |
4713.76 |
转 SQL1 无缓存 |
50 |
4 |
13 |
28 |
10266.72 |
转 SQL2 无缓存 |
50 |
9 |
29 |
49 |
4746.66 |
转 SQL3 无缓存 |
50 |
81 |
165 |
222 |
569.96 |
转 SQL1 缓存 |
50 |
1 |
2 |
4 |
34075.55 |
转 SQL2 缓存 |
50 |
2 |
3 |
8 |
26932.50 |
转 SQL3 缓存 |
50 |
7 |
23 |
39 |
5521.49 |
直 SQL1 |
50 |
2 |
7 |
23 |
16353.33 |
直 SQL2 |
50 |
3 |
10 |
36 |
10785.25 |
直 SQL3 |
50 |
66 |
127 |
166 |
698.90 |
直 SQL1 缓存 |
50 |
1 |
2 |
4 |
33175.86 |
直 SQL2 缓存 |
50 |
2 |
3 |
8 |
26798.36 |
直 SQL3 缓存 |
50 |
7 |
24 |
47 |
5441.85 |
8.4.2.2. 测试结论
表 3-3 TPS 对比表格
单线程 |
20线程 |
50线程 |
||||
---|---|---|---|---|---|---|
对比项 |
直连 |
LTSQL |
直连 |
LTSQL |
直连 |
LTSQL |
无缓存SQL1 |
2683 |
2189 |
15195 |
10515 |
16353 |
10267 |
无缓存SQL2 |
1066 |
799 |
9763 |
4840 |
10785 |
4747 |
无缓存SQL3 |
52 |
48 |
618 |
510 |
699 |
570 |
有缓存SQL1 |
6731 |
6793 |
33059 |
32707 |
33176 |
34076 |
有缓存SQL2 |
5430 |
5355 |
26640 |
25816 |
26798 |
26933 |
有缓存SQL3 |
422 |
459 |
4714 |
4757 |
5442 |
5521 |
按照 TPS 计算平均时间(微秒 us),整理表格如下:
表 3-4耗时(微秒)对比表格-1
1线程 |
3线程 |
|||||
---|---|---|---|---|---|---|
对比项 |
直连 |
LTSQL |
耗时比 |
直连 |
LTSQL |
耗时比 |
无缓存SQL1 |
373 |
457 |
122.54% |
480 |
566 |
118.01% |
无缓存SQL2 |
938 |
1251 |
133.35% |
991 |
1380 |
139.28% |
无缓存SQL3 |
19221 |
20880 |
108.63% |
20209 |
22962 |
113.62% |
有缓存SQL1 |
149 |
147 |
99.09% |
199 |
196 |
98.27% |
有缓存SQL2 |
184 |
187 |
101.38% |
246 |
247 |
100.15% |
有缓存SQL3 |
2370 |
2179 |
91.97% |
2266 |
2257 |
99.60% |
表 3-5耗时(微秒)对比表格-2
20线程 |
50线程 |
|||||
---|---|---|---|---|---|---|
对比项 |
直连 |
LTSQL |
耗时比 |
直连 |
LTSQL |
耗时比 |
无缓存SQL1 |
1316 |
1902 |
144.51% |
3057 |
4870 |
159.28% |
无缓存SQL2 |
2049 |
4133 |
201.73% |
4636 |
10534 |
227.22% |
无缓存SQL3 |
32369 |
39204 |
121.12% |
71541 |
87726 |
122.62% |
有缓存SQL1 |
605 |
611 |
101.08% |
1507 |
1467 |
97.36% |
有缓存SQL2 |
751 |
775 |
103.19% |
1866 |
1856 |
99.50% |
有缓存SQL3 |
4243 |
4204 |
99.08% |
9188 |
9056 |
98.56% |
通过整理的结果可发现:
如果是走了缓存的,性能与直连几乎无差异(大部分业务场景都可以走入缓存)
如果是完全动态的 SQL,单线程的性能损失最高大约在 30% 左右;如果外部时间较长,耗时会变得不明显
带有 IO 的操作,如果计算需要的 CPU 变高,在并发数超越 CPU 核心数的时候会放大这种差异,甚至可增加一倍耗时
实际应用中很少会有不能缓存的 SQL。不能缓存的 SQL 可能是以下几种情况:
MyBatis 中使用 ${} 符号来拼接 SQL,每次都不同
MyBatis 中使用大量的 <if> ,具有非常大量的排列组合,且每次的条件分支都不相同
全动态 SQL,每次都不相同
而以下场景是可以缓存的:
MyBatis 使用 #{} 绑定了变量的。因为变量不视为 SQL,所以可以用缓存
8.4.3. 启动时间测试
测试方法:
启动一个 jres 服务,通过统一 sql 连接到一个 PostgreSQL 数据库,执行 select 1,然后关闭应用
启动一个 java 程序,仅计算统一 SQL 的类加载时间
Jres 应用测试次数为 10 次,结论如下:
表 3-6 Jres 服务启动时间对比
用例 |
平均耗时(ms) |
---|---|
用 Maven 加载统一 SQL |
4274 |
用 unisql.lib.full-path加载统一SQL |
4217 |
用 unisql.lib.dir 加载统一 SQL |
4162 |
不加载统一 SQL |
3980 |
类加载的案例为在 300s 内反复启动,取纯粹类加载消耗的时间的平均值,结论如下:
表 3-7类加载平均时间对比
用例 |
平均耗时(ms) |
---|---|
用 Maven 加载统一 SQL |
267.61 |
用 unisql.lib.full-path 加载统一SQL |
207.01 |
用 unisql.lib.dir 加载统一 SQL |
210.01 |
结论:统一 SQL 类加载时间大约在 200ms 左右,主要耗时为 jnr 的动态库加载机制;使用 Maven 的加载方式会慢 60ms ,其中包含了从 jar 包中解压动态库字节流并写入到文件的时间。
对于一般的 Jres 应用,使用了统一 SQL 后,应用启动耗时会增加 200ms - 300ms。
8.5. 并发性能损耗大分析过程
为详细验证性能组件的真实性能与损耗,使用纯 GO 调用、GO 通过 CGO 调用、GO 调用动态库、C 语言调用动态库、Java 调用五种场景,用三种复杂程度的 SQL来验证 SQL 解析功能的性能,测试机器为 32 cpu 服务器,结果较多,没有全部列出。
sql1 是 105 字节的简单 select
sql2 是 2305 字节的 select
sql3 是 12716 字节的 select
单线程执行情况如下:
表 4-1 单线程对比
场景 |
sql1 |
sql2 |
sql3 |
---|---|---|---|
Pure GO |
17.84 |
168.67 |
1157.31 |
GO with CString |
18.81 |
170.39 |
1182.83 |
GO with dynamic lib |
22.62 |
175.60 |
1162.94 |
C with dynamic lib |
21.84 |
167.58 |
1118.42 |
Java |
24.18 |
184.39 |
1235.37 |
结论:Java 调用会带来少量损耗,在复杂 sql 场景下大约有 5%。
为对比程序详细性能,对程序进行了分段的测试,与直连 LightDB 做了对比。
测试 PG 直接执行的方法是:使用 c 语言 libpq 库,直连到本地数据库,执行相同的 SQL,库中无数据。
PG 日志 elapsed 是执行了 sql 后,在 lightdb-x 日志中找到 PARSER STATISTICS 下方的 system usage stats ,查看 elapsed 项的时间。
结果如下:
表 4-2 分拆程序步骤测试结果
场景 |
sql1 |
sql2 |
sql3 |
---|---|---|---|
PG 日志 elapsed |
2 |
27 |
156 |
PG 直接执行 |
39.84 |
88.01 |
275.92 |
GO 仅解析 |
11.55 |
94.65 |
585.43 |
GO 解析+转换 |
15.18 |
126.74 |
893.42 |
GO 解析+转换+转换为SQL |
17.84 |
168.67 |
1157.31 |
结论:GO 编写的解析功能性能不如 PG 中 C 语言的 实现,从 sql3 看出差异大约在 1倍左右;
转换时间大约是解析时间的 35%;
将结果还原成 SQL 的时间约为解析时间的 30%。
以 sql2 为基准,各种测试方式的 CPU 使用率如下:
表 4-3 多线程 CPU 使用率
场景 |
1 |
2 |
3 |
4 |
8 |
16 |
32 |
64 |
---|---|---|---|---|---|---|---|---|
Pure GO |
100 |
200 |
297 |
394 |
760 |
1380 |
1870 |
2069 |
GO with CString |
100 |
201 |
300 |
398 |
764 |
1389 |
1701 |
1764 |
GO with dynamic lib |
103 |
204 |
307 |
407 |
781 |
1458 |
1937 |
1787 |
C with dynamic lib |
103 |
205 |
307 |
337 |
779 |
1437 |
2109 |
1993 |
Java |
106 |
209 |
311 |
411 |
777 |
1438 |
2133 |
2029 |
以 sql2 为基准,各种测试方式的 单次转换响应时间为:
表 4-4 多线程单次转换响应时间
场景 |
1 |
2 |
3 |
4 |
8 |
16 |
32 |
64 |
---|---|---|---|---|---|---|---|---|
Pure GO |
169 |
190 |
204 |
215 |
277 |
439 |
901 |
1712 |
GO with CString |
170 |
197 |
211 |
234 |
318 |
513 |
1038 |
1890 |
GO with dynamic |
176 |
205 |
228 |
254 |
368 |
587 |
1069 |
2215 |
C with dynamic |
168 |
201 |
225 |
293 |
362 |
586 |
978 |
2053 |
Java |
184 |
216 |
239 |
266 |
374 |
583 |
1010 |
2065 |
以上两表格结论:
从 16并发与 32 并发的数据来看,统一 SQL 无法打满 CPU,原因推测是内存分配方面的原因,暂未深入分析。
表 4-5 纯go下多线程转换测试结果(压测持续30秒)
场景 |
1 |
8 |
16 |
32 |
---|---|---|---|---|
256字节的SQL - TPS |
28714.29 |
27106.58 |
41279.42 |
38849.79 |
256字节的SQL – 单次转换平均耗时(us) |
34.83 |
36.89 |
24.23 |
25.74 |
4000字节的SQL - TPS |
2837.08 |
4517.92 |
5926.89 |
7788.56 |
4000字节的SQL – 单次转换平均耗时(us) |
352.48 |
221.34 |
168.72 |
128.39 |