【性能优化】6.2 [外键关联] 临时地址化
6.2 临时地址化
地址是个内存概念,外键地址化只能在全内存时实施,但大数据常常需要外存计算。
我们先来考虑事实表大而维表仍然较小的情况,这也是现实中比较常见的情况。事实表用于存储不断增长的事件,很容易变得很大。维表用于存储代码类信息,增长变化不大,规模相对稳定,不会太大。
仅维表在内存中,事实表的外键就不能事先地址化了,也就是不能实现预关联了,只能临时建立关联。
A |
|
1 |
=file("product.btx").import@b().keys@i(id) |
2 |
=file("orders.btx").cursor@b(p_id,quantity) |
3 |
=A2.switch(p_id,A1) |
4 |
=A3.groups(p_id.vendor;sum(p_id.price*quantity)) |
可以在游标上使用 switch 函数实现外键地址化,它会返回一个延迟游标,在取数的时候才会进行实质性的关联计算。A4 运算要基于 A3 的返回结果才可以,不像全内存时可以基于原数据表运算,因为全内存时的 switch 会改变原数据表的外键字段,而现在的关联是在游标读取过程中做的。
这种方法称为临时地址化。可以复用的只有维表上建立好的索引,查找维表记录的动作在每次运算时都要做一遍,性能会比全内存时差很多。
理论上临时地址化算法还是会比哈希连接算法更有优势。对于内存装不下的大表,哈希连接时要把这个表的记录按哈希值分成若干个可以被内存装下的几部分,我们俗称为分堆。然后用哈希值对应的堆之间再做内存哈希连接。分堆会产生额外的外存写读动作,性能会变差。而临时地址化方法并不会产生外存缓存数据,没有多余的写和读动作。
不过,数据库碰到这种一大一小两个表的连接运算时并不会严格执行哈希分堆操作,通常会把小表读进内存,再分批读入大表数据做内存连接,实际的复杂度和表现出来的性能都和上面的代码区别不大。
关系代数体系理论上不能区分维表和事实表,只能按表的大小区分。数据库优化器在两个表时容易根据表的大小设计出正确的执行路径,但表较多且关联复杂时则可能被“搞晕”,不能设计出合理的执行路径,而去执行原始的哈希分堆算法。所以我们会观察到即使只有一个大表,在关联表较多时也可能出现性能急剧下降的状况。
而从概念上区分了维表和事实表后,可以明确地先把维表读入内存,建好索引并在维表之间做好预关联,之后遍历事实表计算时,和全内存时除了事实表需要用游标读入且只能临时建立和维表的关联外,整体结构并没有差别,不可能出现产生缓存文件的情况。而且,维表上的索引和维表之间的预关联仍然可以复用。
A |
|
1 |
=file("area.btx").import@b().keys(aid) |
2 |
=file("product.btx").import@b() |
3 |
=A2.join(a_id,A1,~:area).keys@i(pid) |
4 |
=file("orders.btx").cursor@b(p_id,a_id,quantity) |
5 |
=A4.join(p_id,A3,~:product;a_id,A1,~:area) |
6 |
=A5.select(product.area.state==area.state ) |
7 |
=A6.groups(product.vendor;sum(product.price*quantity)) |
A1、A2、A3 做的预处理动作可以在系统启动时执行。A5 中关联维表时也可以复用 A1 和 A3 的索引。
join 和 switch 函数都可以基于多路游标实现并行计算,本例中的 A4 可以用 cursor@m 选项写成多路游标,后面的代码不用改变。上一节的内存连接运算也可以转换成内存多路游标实现并行计算。