@Greptime: GreptimeDB 1.0 将这三者作为内置SQL窗口函数发布。无需单独服务的异常评分,告警阈值就是一个WHERE子句……

X AI KOLs Following 产品

摘要

GreptimeDB 1.0 引入了三个用于异常检测的内置SQL窗口函数(Z-Score、MAD、IQR),使得无需外部服务即可直接在SQL中进行异常评分。

GreptimeDB 1.0 将这三者作为内置SQL窗口函数发布。无需单独服务的异常评分,告警阈值就是一个WHERE子句。 Z-Score 是大多数人用来标记指标突增的工具。但它在这项任务中表现不佳。 以一个大约为10的平坦序列为例,加入一个80的异常值。Z-Score 给该点的评分约为2。MAD 评分为155,IQR 为136。 原因:异常值拉高了自身的均值和标准差,因此它不再看起来远离它刚刚污染的平均值。中位数和四分位数不受单个点的影响,因此 MAD 和 IQR 仍能捕获它。 https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions……
查看原文
查看缓存全文

缓存时间: 2026/06/05 05:11

GreptimeDB 1.0 内置了全部三种 SQL 窗口函数。无需额外服务即可完成异常评分,告警阈值就是一个 WHERE 子句。

Z-Score 是大多数人用来标记指标尖峰的工具。但它并不适合这项任务。

取一组在 10 附近平稳的序列,中间插入一个值为 80 的离群点。Z-Score 对该点的评分为约 2。MAD 评分为 155,IQR 评分为 136。

原因在于:离群点拉高了自身的均值和标准差,因此它相对于刚被污染的平均值不再显得那么远。中位数和四分位数不会因为一个点而移动,因此 MAD 和 IQR 仍然能捕捉到它。

https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions…


SQL 中的异常检测:GreptimeDB 的三种评分函数

来源:https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions

无需额外脚本,无需第三方机器学习服务。一条带有窗口函数的 SQL 查询即可为每个数据点计算异常评分。

监控与可观测性工作有一个共同需求:自动标记指标突然异常的数据点。通常的做法是将数据导出到数据库外,交给 Python 脚本或独立的异常检测服务处理,再将结果写回。这个流程很长,并且增加了数据搬运和维护额外组件的成本。

GreptimeDB 1.0 内置了三种统计异常评分函数,可以直接在 SQL 中为每一行计算异常评分。数据永远不会离开数据库,你的告警阈值就是一个 WHERE 子句。本文将逐一介绍每个函数的作用、何时使用哪个函数,以及如何将它们写入一个实际可运行的查询。

这三个函数分别是 anomaly_score_zscoreanomaly_score_madanomaly_score_iqr。它们都是窗口函数,必须与 OVER 子句一起使用。

注意:这三个函数是 GreptimeDB 新添加的,目前属于实验性功能。它们的行为或签名在后续版本中可能还会变化,因此请根据你运行的版本查阅文档。

无论使用哪个函数,以下规则都适用:

  • 当窗口中没有足够有效的(非 NULL)数据点时,函数返回 NULL。因此时序的前几行通常为 NULL,仅仅是因为样本量尚未达到。
  • 评分为 0.0 表示该值不异常;评分越大,值越异常。
  • 当窗口内的离散度(标准差 / MAD / IQR)为 0,但当前值与窗口中心存在偏差时,函数返回 +inf,意味着在完全平坦的窗口中任何偏差都被视为无限异常。如果你的下游系统无法处理无穷大,请在查询中过滤掉这些结果。

下面我们逐一介绍每个函数。

anomaly_score_zscore:直观但对离群点不鲁棒 (https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions#anomaly-score-zscore-intuitive-but-not-robust-to-outliers)

Z-Score 是最经典的方法,其公式也最为直接:

score = |x − mean| / stddev

它衡量当前值距离窗口均值有多少个标准差:距离越远,评分越高。

Z-Score 衡量一个值距离均值有多少个标准差图 1:Z-Score 以标准差为单位测量与均值的距离sql

anomaly_score_zscore(value) OVER (window_spec)

最小有效样本数为 2(它使用总体标准差,除以 n)。如果有效点少于 2 个,则返回 NULL。

Z-Score 的局限性在于:均值和标准差都会受到离群点本身的影响。一个大的离群点进入窗口会同时拉高均值和标准差,这意味着离群点稀释了自身的异常评分。你可以从下面的完整示例中直接观察到:对于同一个离群点,Z-Score 的评分大约为 2,而另外两个函数返回的评分都超过 100。

何时使用:当数据相当干净且稳定,你只需要一个粗略的偏差信号时,Z-Score 计算最快且足够好用。

anomaly_score_mad:对单点离群点更鲁棒 (https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions#anomaly-score-mad-more-robust-for-single-point-outliers)

MAD 代表中位数绝对偏差(Median Absolute Deviation)。它将 Z-Score 中的均值替换为中位数,将标准差替换为 MAD:

score = |x − median| / (MAD × 1.4826)

中位数对离群点不敏感——少数几个极端值几乎不会改变中位数,但会显著改变均值。这使得 MAD 比 Z-Score 鲁棒得多,更适合单点离群点。

MAD 以中位数为中心,离群点无法像拉高均值那样拉高它图 2:离群点拉高了均值,但中位数保持不变,因此 MAD 保持鲁棒公式中的 1.4826 是一个一致性因子,使得对于正态分布的数据,MAD 评分渐进等价于 Z-Score。换句话说,当数据干净时,MAD 和 Z-Score 产生类似的评分;MAD 只有在存在离群点时才会显示其优势。这种设计的好处是,你在切换两个函数时无需重新校准阈值。

sql

anomaly_score_mad(value) OVER (window_spec)

有一个值得注意的细节:MAD 的最小有效样本数为 3,比 Z-Score 多一个。原因在于,当只有 1-2 个样本时,MAD 几乎总是 0,而 MAD 为 0 会触发前面提到的 +inf 情况,产生大量无意义的“无限异常”结果。将阈值提高到 3 正是为了避免这些虚假告警。

anomaly_score_iqr:可调阈值,非常适合告警 (https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions#anomaly-score-iqr-tunable-threshold-ideal-for-alerting)

IQR 代表四分位距(Interquartile Range),对应于统计学中的 Tukey 栅栏(Tukey Fences)。它的思路与前两个函数不同:不是衡量“距离中心有多远”,而是判断一个值是否越过了栅栏,以及越过了多少。

IQR 根据四分位数构建栅栏,只对越过栅栏的值进行评分图 3:IQR 根据 Q1 和 Q3 构建栅栏;栅栏内的值评分正好为 0它比另外两个函数多一个参数 k

sql

anomaly_score_iqr(value, k) OVER (window_spec)

栅栏定义为下界 Q1 − k×IQR 和上界 Q3 + k×IQR。评分规则如下:

  • 值低于下界:score = (Q1 − k×IQR − value) / IQR
  • 值高于上界:score = (value − Q3 − k×IQR) / IQR
  • 值在栅栏内:score = 0.0

k 是一个非负的 DOUBLE,用于控制栅栏的松紧程度。1.5 对应标准的 Tukey 栅栏,3.0 对应更宽松的远界栅栏。k 越大,值需要越极端才会被标记;负值会使函数返回 NULL。

IQR 天然适合告警:栅栏内的每个值评分正好为 0,边界清晰,不会像 Z-Score 和 MAD 那样为正常值分配较小的分数。其最小有效样本数也是 3(采用线性插值,Q1≠Q3 至少需要 3 个点)。

何时使用:当你想要一个清晰的“正常/异常”二元边界,并希望通过调整 k 来控制灵敏度时——例如,从 1.5 开始,如果告警过于频繁则提高到 3.0。

一个完整可运行的示例 (https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions#a-complete-runnable-example)

下面我们创建一个传感器表,写入一段稳定的数据,在中间插入一个离群点(80.0),然后一次性应用所有三个函数。

sql

`` CREATE TABLE sensor_data ( host STRING, val DOUBLE, ts TIMESTAMP TIME INDEX, PRIMARY KEY (host) );

INSERT INTO sensor_data VALUES (‘web-1’, 10.0, ‘2025-01-01 00:00:00’), (‘web-1’, 11.0, ‘2025-01-01 00:01:00’), (‘web-1’, 10.5, ‘2025-01-01 00:02:00’), (‘web-1’, 10.8, ‘2025-01-01 00:03:00’), (‘web-1’, 80.0, ‘2025-01-01 00:04:00’), – outlier (‘web-1’, 10.3, ‘2025-01-01 00:05:00’), (‘web-1’, 11.2, ‘2025-01-01 00:06:00’); ``

所有三个函数共享相同的窗口定义。为了避免重复,使用 WINDOW 子句定义单个命名窗口 w,并使用 ROUND 保留两位小数以提高可读性:

sql

SELECT ts, val, ROUND(anomaly_score_zscore(val) OVER w, 2) AS zscore, ROUND(anomaly_score_mad(val) OVER w, 2) AS mad, ROUND(anomaly_score_iqr(val, 1.5) OVER w, 2) AS iqr FROM sensor_data WINDOW w AS ( PARTITION BY host ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) ORDER BY ts;

输出如下:

text

+---------------------+------+--------+--------+-------+ | ts | val | zscore | mad | iqr | +---------------------+------+--------+--------+-------+ | 2025-01-01 00:00:00 | 10 | NULL | NULL | NULL | | 2025-01-01 00:01:00 | 11 | 1 | NULL | NULL | | 2025-01-01 00:02:00 | 10.5 | 0 | 0 | 0 | | 2025-01-01 00:03:00 | 10.8 | 0.6 | 0.4 | 0 | | 2025-01-01 00:04:00 | 80 | 2 | 155.58 | 136.5 | | 2025-01-01 00:05:00 | 10.3 | 0.46 | 0.67 | 0 | | 2025-01-01 00:06:00 | 11.2 | 0.38 | 0.67 | 0 | +---------------------+------+--------+--------+-------+

有两个细节值得注意。

首先,mad 和 iqr 列的前两行为 NULL,而 zscore 不是。这就是最小样本数差异的体现:在第二行(00:01),窗口中只有 2 个点,满足 Z-Score(min=2),但不满足 MAD 或 IQR(min=3)。

其次,对于同一个离群点(val=80),Z-Score 只给出 2,而 MAD 给出 155.58,IQR 给出 136.5。这证实了前面提到的 Z-Score 自我稀释问题:值 80 同时拉高了均值和标准差,因此相对于已经被污染的均值,它并未偏离那么多标准差。MAD 和 IQR 基于中位数和四分位数,离群点无法动摇它们,因此它们正确地将该点识别为强异常。

结论很清楚:要捕捉单点离群点,请信任 MAD 或 IQR,而不是 Z-Score。

实际应用:直接过滤异常行 (https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions#in-practice-filtering-anomalous-rows-directly)

得到评分后,你通常需要的不是完整的评分表,而是单独的异常行。窗口函数不能直接放入 WHERE 子句中,因此将查询包装在子查询中,并在外层查询中过滤:

sql

SELECT * FROM ( SELECT host, ts, val, ROUND(anomaly_score_mad(val) OVER ( PARTITION BY host ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ), 2) AS mad FROM sensor_data ) WHERE mad > 3.0 ORDER BY host, ts;

只剩下异常行:

text

+-------+---------------------+------+--------+ | host | ts | val | mad | +-------+---------------------+------+--------+ | web-1 | 2025-01-01 00:04:00 | 80 | 155.58 | +-------+---------------------+------+--------+

阈值 3.0 是一个常见的起点(大致匹配“超过 3 个标准差”的直觉),但并非固定不变。请根据你的数据噪声水平进行调整。

选择窗口同样重要 (https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions#choosing-the-window-matters-just-as-much)

理解三个函数之间的区别很重要,但在实际中,OVER 子句中的窗口范围往往影响更大。上面的示例使用了一个累积窗口:

sql

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW

它从序列的开始一直累积到当前行。优点是样本随着时间增长,评分变得更稳定;缺点是较旧的数据持续参与计算,使其对缓慢的趋势漂移不敏感。

如果你的指标具有日内周期,或者随着每次发布而总体上升,那么只考虑最近 N 个点的滑动窗口更合适:

sql

ROWS BETWEEN 4 PRECEDING AND CURRENT ROW

这样,“正常”的定义会跟随近期数据,旧点会自动退出窗口。另外,不要省略 PARTITION BY host;它确保每个主机和每个序列独立计算,一台机器的基线不会应用到另一台机器上。

总结 (https://greptime.com/blogs/2026-06-03-greptimedb-anomaly-detection-functions#summary)

  1. **默认使用 MAD。**单点尖峰是监控数据中最常见的情况,MAD 对它们最敏感,同时最不容易被离群点本身欺骗。
  2. 使用 IQR 进行告警阈值设定。“栅栏内为 0”的特性使得阈值决策清晰,你可以通过 k 调整灵敏度(先尝试 1.5)。
  3. **使用 Z-Score 进行快速粗略的筛选。**当数据干净且你只需要一个粗略信号时它有效,但不要指望它能捕捉极端离群点。
  4. **过滤异常行 = 子查询 + 外层 WHERE。**窗口函数不能直接放入 WHERE 子句;多加一层即可解决。
  5. **先确定窗口,再选择函数。**累积窗口倾向于稳定性,滑动窗口紧跟近期基线;始终使用 PARTITION BY 来分离不同的序列。

这三个函数都是纯统计方法,用于检测相对于窗口中其他点的离群点。它们不进行趋势预测或季节性异常检测,这些需要更重的模型。但对于“指标突然跳变”(绝大多数告警背后的情况),一条 SQL 查询就能搞定,无需维护单独的数据管道。

这三个函数仍处于实验阶段,我们计划继续扩展 GreptimeDB 的异常检测能力。如果你遇到问题或希望看到特定的异常检测方法,请提交 issue 告诉我们:GreptimeTeam/greptimedb Issues (https://github.com/GreptimeTeam/greptimedb/issues)。

有关完整的参数参考和退化情况表,请参阅官方文档:异常检测函数 - GreptimeDB 文档 (https://docs.greptime.com/reference/sql/functions/anomaly/)。

相似文章

ggsql:面向 SQL 的图形语法

Lobsters Hottest

ggsql 是一款 Alpha 版本工具,它将图形语法的可视化能力引入 SQL,允许用户在 Quarto、Jupyter、Positron 和 VS Code 中利用 SQL 语法构建结构化、模块化的可视化图表。