PAYATHON 2026

Kusto 动态计算时间范围后查询变慢,如何优化?

数据小陈

结论

toscalar() 可以将查询结果求值为标量常量,但该值不一定在查询编译时已知,也不能保证它与时间字面量生成相同的执行计划。仅凭这两段代码,无法断定慢查询是由时间索引失效导致的。toscalar() 文档

第一条查询使用固定时间,没有用到前面计算出的 mostRecent;第二条查询需要先获取表中的最大时间,再进行筛选。应先测量这部分额外计算的成本,再判断动态边界是否影响数据读取。

两条查询的实际区别

第一条查询中:

let foo = todatetime('2026-03-17 08:00');

最终结果不依赖 mostRecent、twelveHoursPrior 或 bar。虽然这些定义出现在查询文本中,但不能证明查询执行了求最大时间的计算。Kusto 的命名表达式在被引用时求值,相关机制见命名表达式说明。

第二条查询中:

let foo = todatetime(bar);

筛选下界依赖 summarize max(timestamp),因此必须先取得这个结果。查询增加了一个访问 UserApiRequests 的计算阶段,实际需要读取多少数据,要通过执行统计判断,不能直接认定为全表扫描。

两条查询的下界也未必相同。除非动态计算结果恰好等于 2026-03-17 08:00,否则它们处理的数据范围可能不同。

先保留 datetime,去掉字符串往返转换

假设 timestamp 的类型为 datetime,可以直接将整个下界计算放进 toscalar():

let windowStart = toscalar(
    UserApiRequests
    | summarize WindowStart = max(timestamp) - 12h
);
UserApiRequests
| where timestamp between (windowStart .. now())
| top 5 by timestamp asc

这段代码保留了原查询的筛选语义:从最新事件时间往前 12 小时,一直到 now(),再取时间最早的 5 条记录。

这种写法去掉了 datetime → string → datetime 转换,整个下界表达式也直接返回标量。写法更直接,但不能保证解决性能问题。

时间条件应直接作用于原始 datetime 列,并尽早执行。Kusto 官方说明,对 datetime 列的筛选可以利用时间索引排除不相关的数据分片。查询最佳实践

怎样定位真正的耗时来源

可以按以下顺序比较,区分边界计算和后续筛选各自的成本:

  1. 单独执行 UserApiRequests | summarize MostRecent = max(timestamp),记录耗时和资源消耗。
  2. 用实际返回的时间计算下界,以该下界为字面量执行筛选查询,保证比较时两条查询的下界一致。
  3. 执行动态边界版本,比较服务提供的 CPU、数据读取量和执行统计,同时尽量保持查询范围、返回列及缓存条件一致。

如果求最大时间本身就很慢,应优先处理这一阶段。如果它很快,但动态版本的数据读取量明显增加,才有依据继续排查边界表达式对筛选优化的影响。

再次套用 toscalar() 并不能强制使用时间索引。如果动态版本持续明显较慢,可以由程序自动分两次请求:先获取最新时间,再把边界作为第二次查询的参数传入。这样无需手动修改查询,但两个请求之间可能有新数据写入,因此应固定第二次查询使用的边界,并明确所需的一致性要求。

按小时统计时,需要先确定窗口含义

原查询的区间是 [mostRecent - 12h, now()]。如果存在日志延迟,这个区间通常超过 12 小时;top 5 只是抽取记录,不能用于每小时计数。

如果需要的是“以最新事件所在小时为界,统计此前 12 个完整的 UTC 小时”,可以使用:

let mostRecent = toscalar(
    UserApiRequests
    | summarize MostRecent = max(timestamp)
);
let windowEnd = bin(mostRecent, 1h);
let windowStart = windowEnd - 12h;
UserApiRequests
| where timestamp >= windowStart and timestamp < windowEnd
| summarize EventCount = count() by Hour = bin(timestamp, 1h)
| order by Hour asc

例如,最新事件时间是 10:37,这里统计的是前一天 22:00 到当天 10:00,不包含 10:00 及之后的记录。这与“从最新事件时间向前滚动 12 小时”是两种窗口口径,需要按业务需求选择。

[windowStart, windowEnd) 使用左闭右开区间,便于相邻窗口避免重复计数。按 bin(timestamp, 1h) 分组时,没有记录的小时不会自动生成结果行;需要零值行时,应另行补齐时间桶。summarize 文档

最新事件时间只能说明已有某条记录到达,不能证明此前的所有日志都已到齐。即使选择了完整小时,乱序或迟到日志仍可能改变计数。若要求最终准确的小时统计,还需要日志系统提供完成标记,或根据已确认的延迟范围设置等待与补算机制。

备注:内容仅供参考。