一次相差 2378 倍的 SQLx 查询:TeaQL 如何把业务边界变成执行计划
内容提要
该文通过MusicBrainz数据集测试,对比SQLx与TeaQL查询性能。直观SQLx全局窗口查询耗时5871毫秒,而先选根对象的两阶段查询仅2.5毫秒,差距达2378倍。TeaQL通过声明业务边界(如根对象数量、关联限制),自动生成优化执行计划,避免处理无关数据。文章强调性能差异源于执行计划的数据量,而非驱动本身,并指出TeaQL优势在于减少手工维护执行细节,确保治理一致性。
延伸解读
性能差异的本质:执行计划的数据量
文章强调,2378倍的性能差距并非源于SQLx或TeaQL的驱动性能,而是两种执行计划处理的数据量不同。直观的全局窗口查询需要对约270万行Relation进行排名,而先选根对象的两阶段查询只需处理100个Recording的关联数据。这提醒开发者,优化查询时,应关注数据库实际处理的数据量,而非仅看SQL语句的简洁性或查询次数。
手工优化的维护风险
优化后的SQLx方案虽然性能优异,但需要开发者手工维护两条SQL,并确保两阶段之间的过滤条件、租户隔离、权限范围等治理语义一致。文章指出,手工优化容易引入遗漏,如子查询未继承租户条件或权限范围,尤其在AI编程工具辅助时,生成的SQL可能更快但意外加载其他租户的数据。因此,治理一致性是手工优化的重要挑战。
TeaQL的声明式边界优势
TeaQL通过声明业务边界(如根对象数量、关联限制)自动生成优化执行计划,避免了手工维护执行细节。其代码量与直观SQL相当,但优势在于将根对象边界、关联边界和治理语义统一声明,由运行时转化为执行步骤,确保租户隔离、权限等策略不丢失。这体现了声明式API在减少手工优化风险方面的价值。
查询次数不是唯一性能指标
文章指出,单纯用查询次数评价性能不够全面。一次数据库往返的直观查询可能让数据库处理数百万行无关数据,而两条范围明确的查询可能只处理几百行。因此,更关键的问题是数据库为完成业务请求实际做了多少工作,包括扫描、连接、排序和排名的数据量。这提示开发者应关注执行计划的数据范围,而非仅关注SQL语句数量。
Q&A
SQLx 直观的全局窗口查询和先选根对象的两阶段查询在 MusicBrainz 测试中的耗时分别是多少?
直观的全局窗口查询耗时 5871.169 毫秒,先选根对象的两阶段查询耗时 2.469 毫秒,性能差距约 2378 倍。
为什么直观的 SQLx 窗口查询会这么慢?
因为直观查询需要对整个 Relation 集合(约 270 万行)进行分组和排序,才能确定哪些 Relation 属于最终选中的 100 条 Recording,而实际只需要 103 条 Relation。
TeaQL 如何表达这个业务请求?
TeaQL 通过声明式 API 表达请求,例如使用 Q::recordings() 选择根对象,通过 which_have_work_relations() 过滤,然后 select_work_relations_with() 加载关联,并指定排序和限制,最后 order_by_id_desc().limit(100) 限制根对象数量。运行时根据这些语义自动生成优化执行计划。
TeaQL 相比优化后的 SQLx 有什么优势?
TeaQL 的优势在于减少手工维护执行细节,它在一个请求中声明根对象边界、关联边界和治理语义,由运行时自动转化为执行步骤,确保租户隔离、权限、版本策略等治理一致性,而优化后的 SQLx 需要开发者手动维护两条 SQL 并保证一致性。
这个实验证明了什么?没有证明什么?
证明了业务 API 中的语义信息可以帮助运行时避免大量无效的数据库工作,业务边界在执行计划形成前保留可产生显著性能差异。没有证明 TeaQL 的驱动比 SQLx 快,所有窗口函数都慢,多条 SQL 永远优于一条 SQL,或 2378 倍可推广到其他场景。
如何复现这个实验?
实验代码和证据保存在公开仓库 https://github.com/teaql/teaql-runtime-benchmark,其中 B004 测试包含两种 SQLx 查询方案及正确性校验,记录了数据来源、查询结构、运行环境、测量结果等。