一直在摆弄 jOOQ 及其 MULTISET 功能。但对查询的性能有点失望。我假设我正在做一些次优的事情,或者 JSON 反序列化的成本可能比预期的要高。
鉴于流行的 Sakila DB 和这两个查询:
val result: Result<Record3<CustomerId, Int, Int>> = dslContext
.select(
CUSTOMER.CUSTOMER_ID,
PAYMENT.PAYMENT_ID,
RENTAL.RENTAL_ID,
)
.from(CUSTOMER)
.join(PAYMENT).on(PAYMENT.CUSTOMER_ID.eq(CUSTOMER.CUSTOMER_ID))
.join(RENTAL).on(RENTAL.CUSTOMER_ID.eq(CUSTOMER.CUSTOMER_ID))
.orderBy(CUSTOMER.EMAIL)
.fetch()
和
val result: Result<Record3<CustomerId, Result<Record1<Int>>, Result<Record1<Int>>>> = dslContext
.select(
CUSTOMER.CUSTOMER_ID,
multiset(
DSL.select(PAYMENT.PAYMENT_ID)
.from(PAYMENT)
.where(PAYMENT.CUSTOMER_ID.eq(CUSTOMER.CUSTOMER_ID))
),
multiset(
DSL.select(RENTAL.RENTAL_ID)
.from(RENTAL)
.where(RENTAL.CUSTOMER_ID.eq(CUSTOMER.CUSTOMER_ID))
),
)
.from(CUSTOMER)
.fetch()
测量行数和所需时间可得出以下结论:
445483 Records via JOIN. in 466.389250ms
599 Records via MULTISET in 747.627541ms
这相当令人失望......即使我从
CUSTOMER
表中选择更多数据,导致在 JOIN 情况下传输更多重复数据,它在 MULTISET 方面也不会发生太大变化。
我错过了什么吗?
MULTISET
和底层 JSON 聚合技术非常适合小型嵌套集合,而不是大型嵌套集合,在这种情况下有更好的优化方法,包括:
您的示例查询并不真正代表现实世界的用例。您只是获取父表的所有数据以及子表的所有数据,这几乎不会以这种方式完成,如果是的话,那么您可能没有这种嵌套用例,但只需将数据导出为 CSV 或其他格式即可。在实际用例中,
MULTISET
通常可以优于等效的 JOIN 表,但您必须考虑执行计划(与 SQL 一样,而不仅仅是 MULTISET
!)
如果您进行了任何进一步的测量、分析等,您会发现 JSON 序列化/反序列化的开销虽然存在,但毫无疑问,与较差执行计划的效果相比,仍然是微乎其微的。当从子查询收集数据到 JSON 文档时,大多数 RDBMS 仍然会产生各种嵌套循环,而联接可以从哈希联接或合并联接中受益,它们具有更好的算法复杂性。就像对于大型数据集而言,嵌套循环联接不如哈希联接一样,嵌套集合也可能较差。
与 SQL 一样,不要试图建立关于哪种方法比另一种方法更好的一成不变的规则,而是尝试理解为什么事情是这样的,并经常验证你的假设,因为优化者往往会得到更好,并且 RDBMS 没有理由不最终找到将此类嵌套集合循环转换为某种哈希集合的方法。