Polars 明明先 sort,when 结果却按旧顺序返回
Polars 的顺序观察优化可能漏算 when 条件列,在两个分支都是常量时移除排序。检查排序后的行对应关系,局部关闭相关优化,不用更换执行引擎掩盖问题。
LazyFrame 已经按 x 排序,最后只选出 when(x > 1).then(1).otherwise(0),结果仍像按原始行顺序计算。把 x 也选出来,结果又正常。遇到这种差异,应检查优化器是否错误认为结果不观察行顺序。
这项问题在 Polars 2.0 的 streaming 查询中出现,更简单的排序表达式也能触发;并不只影响 streaming。条件分支依赖列,两个返回分支却都是常量时,顺序依赖分析可能忽略条件这一侧,删去仍有意义的排序。不能把所有未指定顺序的 group-by 结果也归到本题。
用明确的小输入检查排序是否被丢掉
下面的例子按 x 升序后,输出应依次对应 1、2、3,即 0、1、1:
import polars as pl
query = (
pl.LazyFrame({"x": [3, 1, 2]})
.sort("x")
.select(pl.when(pl.col("x") > 1).then(1).otherwise(0).alias("flag"))
)
result = query.collect(
optimizations=pl.QueryOptFlags(check_order_observe=False)
)
关闭这一项是局部规避和对照方式,保留其他优化的默认设置。2.1.0 正式接口中有 check_order_observe,但 QueryOptFlags 被标为不稳定接口;固定依赖版本,并在升级时检查参数是否仍存在,不能把它当作永久不变的业务 API。
真实查询中保留原排序键、窗口分组、null 位置与输出列,只对受影响执行入口设置该项。若业务依靠同值行的稳定次序,还需要原来约定的额外排序键;关闭优化不会凭空给相同键补出确定顺序。
加一列不是充分验收
同时选择排序键有时会让优化器保留顺序,可用于定位依赖分析。它也可能改变查询计划,因此“调试时加列就正确”不能证明只输出 flag 的正式查询也正确。
不要只把引擎从 streaming 换成 in-memory 后就关闭问题:同一排序场景在两种引擎中都可能触发。只比较 flag 的计数也不够,乱序不会改变 0 和 1 的总数;应将输出与稳定行身份或预期排序位置对齐。
对于没有排序契约的查询,先明确下游是否本来就不应依赖行顺序。不能为了复现这个问题给所有结果追加任意排序,改变原来的窗口或采样含义。
更换 Polars 版本后,用相同查询计划检查常量分支、列分支、上游排序和窗口条件。恢复默认优化后仍能保持正确的行对应关系,才移除临时选项。


