打开网易新闻 查看精彩图片

每天写FlexibleSearch的SAP Commerce开发者超过10万人,但能把查询延迟从3秒压到30毫秒的不到5%。这不是语法问题——是大多数人根本没搞懂引擎怎么翻译你的花括号。

FlexibleSearch看起来像SQL,实际在跟类型系统打交道。{Product}不是表名,是一整张继承图谱。当你写SELECT {pk} FROM {Product},引擎在后台展开的是Product及其所有子类型:VariantProduct、ApparelProduct、ElectronicsProduct……继承层级越深,生成的SQL越臃肿。

翻译层:你的花括号去哪了

引擎入口在de.hybris.platform.jalo.flexiblesearch.FlexibleSearch,真正的翻译逻辑藏在TranslatedQuery类。HAC控制台能看生成的SQL——跑完查询往下滚,实际执行的语句就躺在结果下方。

翻译过程分三步:解析类型引用、展开继承条件、映射到物理表。类型引用{Product}会被替换成products表,同时追加typepkstring过滤条件确保子类型也被纳入。如果Product有5层继承,WHERE子句会多出5个OR条件。

关键限制:FlexibleSearch只读。所有写入必须走ModelService。这条红线踩过的人不少——试图在HAC里写UPDATE,只会收获一条冷冰冰的报错。

SELECT的隐藏成本

SELECT的隐藏成本

SELECT {pk}是强制底线,但多选字段有讲究。Java代码里用FlexibleSearchService,结果总是PK反查出来的完整Model——额外字段对程序没用,只在HAC调试时省得再点一次。

反模式很常见:SELECT {pk}, {code}, {name}, {description}, {catalogVersion}一口气拉十几个字段,结果只用前三个。数据库IO白白浪费,缓存命中率跟着掉。

子查询语法支持嵌套,但性能陷阱埋在这里:

SELECT {pk} FROM {Product} WHERE {approvalStatus} = {{SELECT {pk} FROM {ArticleApprovalStatus} WHERE {code} = 'approved'}}

外层每扫一行,子查询可能再跑一遍。数据量小没问题,百万级Product表直接卡死。换成JOIN或先查状态PK再传参,延迟能差两个数量级。

分页:没有LIMIT的日子

分页:没有LIMIT的日子

FlexibleSearch语法里没有LIMIT。分页全靠Java API:

打开网易新闻 查看精彩图片

FlexibleSearchQuery query = new FlexibleSearchQuery("SELECT {pk} FROM {Product}");
query.setStart(0);
query.setCount(20);

看起来干净,但setStart用的是OFFSET机制。翻到第1000页,数据库要先扫掉前面999页的数据。深度分页的查询时间随页码线性增长,这是所有OFFSET方案的通病。

生产环境的解法:用上一页最后一条的排序字段做游标,改成WHERE createTime > ? ORDER BY createTime。需要业务层配合,但能把第1000页的查询压到和第1页同量级。

缓存:命中率的隐形杀手

缓存:命中率的隐形杀手

FlexibleSearch结果默认进查询缓存,缓存键是SQL字符串的哈希。问题出在翻译后的SQL——同样的逻辑查询,条件顺序不同、空格多一个,生成的SQL就不同,缓存键跟着变。

团队里三个人写同一个查询,风格各异,缓存里存了三份。更隐蔽的是动态条件:用StringBuilder拼WHERE子句,参数值直接嵌进字符串,每次查询都是新的缓存键。缓存命中率归零,数据库压力爆表。

参数化查询是解药:WHERE {code} = ?code配合query.addQueryParameter("code", value)。引擎把参数值隔离在缓存键之外,同类查询共享缓存条目。

DISTINCT关键字存在,但用起来要谨慎。引擎把DISTINCT下推到数据库,大结果集去重会触发磁盘排序。百万级数据DISTINCT,临时表撑爆磁盘分区的事故我见过两次。

替代方案:业务层用LinkedHashSet去重,或者改查询逻辑避开重复数据。内存去重比磁盘排序快一个数量级,前提是JVM堆给得够。

索引策略常被忽视。FlexibleSearch的WHERE条件翻译后,字段名前会加表别名。{code}变成p_code,如果索引建在code列上,优化器可能认不出来。DBA加索引时得对照翻译后的SQL,不是看FlexibleSearch源码。

复合索引的顺序也有讲究。WHERE {catalogVersion} = ? AND {code} = ?,索引先放catalogVersion还是code,取决于哪个过滤性更强。catalogVersion只有十几个值,code是百万级唯一,顺序反了索引只用到前导列。

执行计划不会看?HAC的FlexibleSearch控制台能导出实际SQL,贴进数据库客户端看EXPLAIN。关注rows估算值和Extra列的Using filesortUsing temporary——这两个出现任意一个,查询就有优化空间。

类型系统的继承查询是性能黑洞。SELECT {pk} FROM {Item}会扫全库,因为Item是所有类型的根。即使加了WHERE {itemtype} = ?,引擎为了保险还是会展开继承条件。明确指定具体类型,别让引擎猜。

最后说一个冷知识:FlexibleSearch查询在HAC里跑得快,到生产环境变慢,八成是连接池和缓存配置差异。HAC用的admin连接池,生产环境是应用连接池,缓存区域也可能不同。本地调优完记得在准环境复测。

你最后一次检查自己写的FlexibleSearch生成的SQL,是什么时候?