JOIN是关系数据库中常用运算,用于把多个表进行关联,关联条件一般是判断某个关联字段的值是否相等。随着关联表的增多或者关联条件越来越复杂,无论理解查询含义、实现查询语句,还是在查询的性能方面,可以说JOIN都是最具挑战的SQL运算,没有之一。
特别是JOIN的性能,一直是个老大难问题。下面我们将基于数据计算中间件(DCM)——集算器,来提供一些提升运算性能的方法。
当然,我们不是介绍如何在写SQL语句时怎么写JOIN,也就是我们假设已经对查询需求有了正确的理解并且能正确地实现SQL。这种情况下,要提升性能,就必须从最基本的提升数据IO配合算法及并行等手段做起。正因如此,如果数据仍然存储在数据库中,那也没什么好办法提速,因为数据库的IO效率很低,又几乎无法并行,即使把运算写得再精巧也无济于事。所以,要提高性能,一定要把数据搬出数据库,我们下面的讨论都是基于这个思路,而集算器正是实现这个思路的利器,甚至神器!
把数据表搬出数据库存储到集算器的集文件中很简单,只要用两行代码:
A | |
1 | =db.cursor("select * from 订单表") |
2 | =file("Order.btx").export@b(A1) |
这两行代码把数据库里订单表的数据导出到集文件Order.btx。
因为数据库IO性能不佳,而且数据量也可能很大,所以这个“搬家”动作可能时间也不短,但还好是一次性的。后面我们的计算都将从集文件中取数。
1 判断 JOIN 的类型
在将数据搬出数据库后,我们需要首先判断JOIN的类型,然后才能采取有针对性的优化措施。
JOIN运算大家都很熟悉,按照SQL的语法定义划分,包括INNER JOIN(内连接)、LEFT JOIN(左连接)、RIGHT JOIN(右连接)、FULL JOIN(全连接)几个类型,这是根据在运算中对空值的处理规则进行划分的。而我们的分析和优化,则会从更贴近需求的语义角度出发,根据各个表的主键参与关联的情况进行划分,总体来说有这么三种:外键表、同维表、主子表。
外键表
当表A的某些字段与表B的主键关联,B称为A的外键表,A表中与B表主键关联的字段称为A指向B的外键。此时A表也称为事实表,B表也称为维表。
表A:Order订单表 | |
ID | 订单编号 |
CustomerID | 客户编号 |
SellerID | 销售编号 |
OrderDate | 订购日期 |
Amount | 订单金额 |
表B:Customer客户表 | |
ID | 客户编号 |
Name | 客户名称 |
Area | 所在区域 |
表C:seller销售人员表 | |
ID | 员工编号 |
Name | 姓名 |
Age | 年龄 |
…… |
这是一个典型的例子,订单表的客户编号与客户表的主键客户编号进行关联,此时A指向B是多对一的关系,即A表有可能存在多条记录指向B表的同一条记录。
这种情况,我们可以把外键字段(例子中的“CustomerID”)的值理解成指向外键表中对应记录的“指针”,而外键表中对应的记录就可以理解成一个对象,而外键表的字段就可以理解为对象的属性, “指针”的作用只是用于找到外键表中对应那条记录。例子中对表A和表B做关联,一定是想获得某些订单的客户的姓名或所在区域等详细信息,这样,如果能写成customerID.name和customerID.area就会更容易理解,这种语法在集算器中也得到了完美的支持。
同时,表A还可以有多个外键表,例如表A的销售编号(SellerID)可以指向一个销售人员信息表C,从而获得该订单销售人员的属性信息。
同维表
表A的主键与表B的主键关联,A和B相互称为同维表。同维表是一对一的关系,JOIN、LEFT JOIN和FULL JOIN的情况都会有,例如:员工表和经理表。
表A:employee员工表 | |
ID | 员工编号 |
Name | 姓名 |
Salary | 工资 |
表B:manager客户表 | |
ID | 编号 |
Allowance | 补贴 |
…… |
这两个表的主键都是员工编号ID,也就是经理也是员工之一,不过因为经理比普通员工多了一些属性,所以需要另用一个经理表来保存。对于这种一对一的情况,逻辑上可以简单地看成一个表来对待。同维表JOIN时两个表都是按主键关联,相应记录是唯一对应的。
主子表
表A的主键与表B的部分主键关联,A称为主表,B称为子表。主子表是一对多的关系,只有JOIN和LEFT JOIN,不会有FULL JOIN,如:订单和订单明细。
表A:Order订单表 | |
ID | 订单编号 |
CustomerID | 客户编号 |
OrderDate | 订购日期 |
…… |
表B:OrderDetail订单明细表 | |
ID | 订单编号 |
NO | 订单序号 |
Product | 订购产品 |
Price | 价格 |
…… |
表A的主键是ID,表B的主键是ID和NO,表A里的一条记录会对应表B里的多条记录。此时,可以把订单明细表里的相关记录看成是订单表的一条记录的属性,该属性的取值是一个集合,而且常常需要使用聚合运算把集合值计算成单值。例如查询每个订单的总金额,可以描述为:
SELECT ID, SUM(OrderDetail.Price) FROM Order
显然,主子表关系是不对等的,而且从两个方向的引用都有意义。从主表引用子表的情况就是通过聚合运算得到一个单值,而从子表引用主表则和外键表类似。
那么,这样划分三种JOIN运算,外键表、同维表、主子表,有什么用处呢?当然是为了优化性能!对于需要优化的JOIN运算,在准确判断是哪种类型基础上,后面的优化才会更加有效。另外,有必要说明一下,这里提到的表A和表B不要求必须是一个实体表,也可能是一个子查询产生的“逻辑表”。
下面我们就开始针对这三种类型以及实际的业务情况进行分析和提速。
详情可以浏览原文地址