怎样用 esProc 提速枚举字段条件过滤
数据表枚举字段的取值是有限几种值,针对枚举字段 f 的过滤条件写成 f =v1 or f=v2 or…或者 f !=v1 and f !=v2 and…,也可能写成 in 或者 not in。数据库要用 f 与 n 个值比较计算,数据表较大的时候比较次数会很多,性能就会比较差,而且 n 越大性能越差。
如果在过滤的时候不再做比较,性能自然大幅提高。esProc 提供的对位序列机制可以起到这样的作用。
下面通过订单表的例子来看怎样使用对位序列,同时比较一下 esProc SPL 和 MYSQL 数据库计算枚举字段条件过滤的性能。
MYSQL 数据库中,订单表 orders 是事实表,存储了 2024 年全年订单,主键是订单号 oid,字段有客户号 cid,日期 odate,雇员姓名 ename,金额 amount,数据量一千万。
cid 和 ename 是枚举字段,cid 对应维表 customer,而 ename 没有对应维表。
客户表 customer 主键是 cid,字段是客户姓名 cname,国家 ccountry。数据量不大,只有 1 万条记录。
测试环境:VMWARE 虚拟机,8 核 CPU,8G 内存,SSD 硬盘。操作系统是 Win11,MYSQL 版本是 8.0。
先下载 esProc https://www.esproc.com/download-esproc/,用标准版就可以了。
安装 esProc 后,试一下 IDE 是否可以正常访问数据库。先把 MYSQL 数据库的 JDBC 放到目录 "[安装目录]\common\jdbc",这是 esProc 的类路径之一:
在 esProc 中建立 MYSQL 数据源:
返回到数据源界面并连接刚才配置的数据源,如果数据源名变成粉色,说明配置成功。
在 IDE 中新建脚本,编写 SPL 语句,连接数据库,执行 SQL 读入 customer 表数据:
A |
B |
|
1 |
=connect("mysql16") |
|
2 |
=A1.query("select * from customer") |
按 ctrl-F9 执行,可以在 IDE 右边看到 A2 的执行结果,很方便。
我们先来确定枚举字段的取值范围。字段 cid 有对应维表,取值范围已经确定了。字段 ename 没有对应维表,需要预先遍历事实表 orders 确定取值范围,生成维表。
这个过程在数据准备阶段完成,同时还要完成事实表中枚举字段的序号化:
A |
|
1 |
=connect("mysql16") |
2 |
=A1.query("select * from customer order by cid").keys@i(cid) |
3 |
=file("customer.btx").export@b(A2) |
4 |
=A1.query("select distinct ename from orders order by ename").keys@i(ename) |
5 |
=file("employee.btx").export@b(A4) |
6 |
=A1.cursor("select oid,cid,ename,odate,amount from orders") |
7 |
=A6.derive(A2.pfind(cid):cnum,A4.pfind(ename):enum) |
8 |
=file("orders.ctx").create(oid,cnum,enum,odate,amount,cnum,enum) |
9 |
=A8.append(A7) |
10 |
>A1.close(),A9.close() |
A2 取出 customer 表,以 cid 为主键,为后面把订单表的 cid 转换为序号做准备。
A4 取出 orders 表中不重复的 ename,以 ename 为主键形成新的维表 employee,为后面把订单表的 ename 转换为序号做准备。
A6 建立 orders 表的游标,A7 把 cid 和 ename 序号化,也就是用 pfind 函数转换成对应维表中的序号。为过滤计算做准备。
ename 这样的字符串字段占用空间较大,且比较计算也更慢,转换成整数会有效提速。
这里保留 ename 和 cid 字段,用来对比使用对位序列和不使用时的性能差异。
A8 把序号化的数据写入订单组表。
数据准备完成后,就可以利用 esProc 为枚举字段条件过滤提速了。
例一, 找出雇员是 James 或者 Luke 的订单,按照日期分组统计订单数量。
SQL 这样写:
select odate,count(1)
from orders
where
ename='James' or ename='Luke'
group by odate;
MYSQL 执行了 28 秒。
esProc 采用对位序列机制计算:
A |
|
1 |
=file("employee.btx").import@b() |
2 |
=A1.(["James","Luke"].contain(ename)) |
3 |
=file("orders.ctx").open().cursor@m(odate;A2(enum)) |
4 |
=A3.groups(odate;count(1)) |
A2 循环计算维表 employee,生成一个同样长度的布尔值序列。循环中用 contain 函数判断集合 ["James","Luke"] 是否包含当前位置的 ename 值,如果包含,则布尔值序列同样位置的成员被赋值成 true,否则就是 false。esProc 把这样的布尔值序列称为对位序列。
如果要计算 not in,只要在 A2 中总括号前加上!就可以了。
A3 对事实表 orders 的游标做条件过滤。在数据准备阶段,enum 字段被赋值为维表的对应序号。这里就用序号去取对位序列的成员,成员是 true 表示满足条件,否则就是不满足。这样就把比较运算转化成了按位置取序列成员,计算复杂度会降低。
esProc 执行时间是 0.5 秒。
不用对位序列机制的代码是这样:
A |
|
1 |
=file("orders.ctx").open().cursor@m(odate;ename=="James" || ename=="Luke") |
2 |
=A1.groups(odate;count(1)) |
执行时间是 0.5 秒。
因为待比较的雇员名只有两个,总数据量也不大,字符串比较运算和按位置取成员的性能差别不明显。
例二, 找出雇员在 James 等 10 人之内的订单,按照日期分组统计订单数量。
这个例子要写 or 就有点多了,SQL 用 in 写:
select odate,count(1)
from orders
where
ename in ('James', 'Rose', 'Luke', 'Tom',…)
group by odate
MYSQL 仍是 28 秒。
esProc 采用对位序列机制:
A |
|
1 |
=file("employee.btx").import@b() |
2 |
=A1.(["James","Rose","Luke","Tom",…].contain(ename)) |
3 |
=file("orders.ctx").open().cursor@m(odate;A2(enum)) |
4 |
=A3.groups(odate;count(1)) |
代码在例一基础上增加待比较的雇员名即可。
esProc 仍是 0.5 秒。
不采用对位机制的代码:
A |
|
1 |
= ["James","Rose","Luke","Tom",…].sort() |
2 |
=file("orders.ctx").open().cursor@m(odate;A1.contain@b(ename)) |
3 |
=A2.groups(odate;count(1)) |
esProc 执行时间是 0.8 秒。待比较的雇员名变多,字符串比较运算和按位置取成员的性能差别就比较明显了。
例三, 找出客户国家是 USA 的订单,统计总金额。
SQL 要使用子查询:
select sum(amount)
from orders
where
cid in (
select cid
from customer
where ccountry='USA')
MYSQL 还是执行了 28 秒。
esProc 对位序列的写法和前两题基本一致:
A |
|
1 |
=file("customer.btx").import@b() |
2 |
=A1.(ccountry=="USA") |
3 |
=file("orders.ctx").open().cursor@m(amount;A2(cnum) ) |
4 |
=A3.total(sum(amount)) |
执行时间 0.5 秒。
不使用对位序列的写法:
A |
|
1 |
=file("customer.btx").import@b().keys@i(cid) |
2 |
=A1.select@i(ccountry=="USA") |
3 |
=file("orders.ctx").open().cursor@m(amount;cid:A2) |
4 |
=A3.total(sum(amount)) |
执行时间 0.5 秒。cid 本身是整形,整数比较计算和按位置取成员的差别也不明显。
测试结果:
MYSQL |
esProc(对位序列) |
esProc(非对位序列) |
|
例一 |
28 秒 |
0.5 秒 |
0.5 秒 |
例二 |
28 秒 |
0.5 秒 |
0.8 秒 |
例三 |
28 秒 |
0.5 秒 |
0.5 秒 |
总共一千万的数据量,用 esProc 对位序列提速枚举字段的过滤条件,在例二字符串比较的情况下,效果比较显著。
如果继续加大数据量,对位序列的效果会更明显。
esProc 准备数据时,从数据库导出数据的时间较长,但这个操作是一次性的,时间长一些也无妨。后续订单表有增量数据的时候,只要定期对组表继续追加即可。
不过维表有数据变更的时候会麻烦一些,需要重新生成 btx。而且事实表序号化的结果依赖于维表的记录次序,如果维表发生插入删除等变化,就要重新生成一次事实表,维护管理成本相对较高。
实际上,针对固定不变的历史数据的计算场景就非常多了,也有不少枚举字段的条件过滤计算亟需提高性能。这些场景很适合采用 esProc 对位序列提速。
英文版