SPL:并行取数技术
JAVA 应用必须通过 JDBC 从数据库中取数,有时候我们会发现,数据库的负担并不重而且 SQL 很简单,但当数据量大到一定程度时取数的速度仍然很慢,这是因为JDBC本身性能不佳。SPL提供了并行运行功能,可以利用多CPU同时取数,提高了取数的效率。
1. 单表并行取数
ORACLE数据库中有一张CUSTOMER表有数据记录1500万行,共8个字段,其中C_CUSTKEY是从1开始的自然数。
并行取数的SPL脚本如下:
A |
B |
|
1 |
=now() |
//记录开始时间 |
2 |
=connect("oracle").query@1x("SELECT COUNT(*) FROM CUSTOMER") |
|
3 |
>n=12 |
//设置并行数,根据电脑的物理CPU核数决定 |
4 |
=n.(range(1,A2+1,~:n)) |
//按总记录数和并行数分段,记录本段开头和下段开头数字 |
5 |
fork A4 |
=connect("oracle") |
6 |
=B5.query@x("SELECT * FROM CUSTOMER WHERE C_CUSTKEY>=? AND C_CUSTKEY<?",A5(1),A5(2)) |
|
7 |
=A5.conj() |
//合并取数结果 |
8 |
=interval@s(A1,now()) |
//计算运行时间 |
并行取数时要把源数据分成相对平均的多个区间。本例中,C_CUSTKEY是从1开始的自然数,因此可以先求出总记录数(A2),再平均分成n段(A4)。然后 A5并行计算(注意:要每个线程各自连接数据库,否则数据库会串行执行同一连接的多个查询,速度并不能加快),再以分段区间为参数使C_CUSTKEY位于本段区间来执行SQL查询。最后合并各线程的取数结果,作为最终结果。
有时不能这么分段,先计算count(*)有可能很慢(如果有WHERE条件的话),结果总时间反而更长了。
实际应用中,还可以采取其它合适的办法设置WHERE条件以获得相对平均的区间。假如C_CUSTKEY是自然数,不论是不是从1开始的,也不论是不是连续的,都可以采用计算它与并行数相除的余数来分段,余数为0,1,2……n-1各为一段,编写脚本如下:
A |
B |
|
1 |
=now() |
//记录开始时间 |
2 |
>n=12 |
//设置并行数,根据电脑的物理CPU核数决定 |
3 |
fork to(n) |
=connect("oracle") |
4 |
=B3.query@x("SELECT * FROM CUSTOMER WHERE MOD(C_CUSTKEY,?)=?", n, A3-1) |
|
5 |
=A3.conj() |
//合并取数结果 |
6 |
=interval@s(A1,now()) |
//计算运行时间 |
fork中的并行线程数和电脑物理CPU核数不一定相等,可以更大一点,运行时同时执行的线程数不会超过核数,超过的线程只能等待有的线程运行结束后有空闲的核时才执行。当分段方法分出的各段数据不太平均时,有的段因数据量少执行很快,就可以尽早让给等待的线程执行,从而实现负载均衡。
2. 多表并行取数
执行多个不同SQL语句取数时也可以使用并行技术,比如要从ORACLE数据库中读取五张表的数据,采用非并行取数的脚本如下:
A |
B |
|
1 |
SELECT * FROM SUPPLIER |
|
2 |
SELECT * FROM PART |
|
3 |
SELECT * FROM CUSTOMER |
|
4 |
SELECT * FROM PARTSUPP |
|
5 |
SELECT * FROM ORDERS |
|
6 |
=now() |
//记录开始时间 |
7 |
=connect("oracle") |
//连接数据库 |
8 |
=[A1:A5].(A7.query(~)) |
//依次执行每条SQL |
9 |
>A7.close() |
//关闭数据库连接 |
10 |
=interval@s(A6,now()) |
//计算运行时间 |
改为并行取数,第7行开始脚本如下:
A |
B |
|
7 |
fork [A1:A5] |
=connect("oracle") |
8 |
=B7.query@x(A7) |
|
9 |
=interval@s(A6,now()) |
//计算运行时间 |
尽管多表并行无法保证数据均匀分布,但并行取数也能让取数性能得到有效提升。