From 7e5fa0ddeb70827c6450e8bfdfa4dc913a54a660 Mon Sep 17 00:00:00 2001 From: jiajingbin Date: Sun, 7 Aug 2022 23:45:23 +0800 Subject: [PATCH 1/6] doc: add 06-stream.md --- docs/zh/07-develop/06-stream.md | 263 ++++++++++++++++++++++---------- 1 file changed, 179 insertions(+), 84 deletions(-) diff --git a/docs/zh/07-develop/06-stream.md b/docs/zh/07-develop/06-stream.md index b2223d15e3..ca92b9098e 100644 --- a/docs/zh/07-develop/06-stream.md +++ b/docs/zh/07-develop/06-stream.md @@ -1,84 +1,179 @@ ---- -sidebar_label: 连续查询 -description: "连续查询是一个按照预设频率自动执行的查询功能,提供按照时间窗口的聚合查询能力,是一种简化的时间驱动流式计算。" -title: "连续查询(Continuous Query)" ---- - -连续查询是 TDengine 定期自动执行的查询,采用滑动窗口的方式进行计算,是一种简化的时间驱动的流式计算。针对库中的表或超级表,TDengine 可提供定期自动执行的连续查询,用户可让 TDengine 推送查询的结果,也可以将结果再写回到 TDengine 中。每次执行的查询是一个时间窗口,时间窗口随着时间流动向前滑动。在定义连续查询的时候需要指定时间窗口(time window, 参数 interval)大小和每次前向增量时间(forward sliding times, 参数 sliding)。 - -TDengine 的连续查询采用时间驱动模式,可以直接使用 TAOS SQL 进行定义,不需要额外的操作。使用连续查询,可以方便快捷地按照时间窗口生成结果,从而对原始采集数据进行降采样(down sampling)。用户通过 TAOS SQL 定义连续查询以后,TDengine 自动在最后的一个完整的时间周期末端拉起查询,并将计算获得的结果推送给用户或者写回 TDengine。 - -TDengine 提供的连续查询与普通流计算中的时间窗口计算具有以下区别: - -- 不同于流计算的实时反馈计算结果,连续查询只在时间窗口关闭以后才开始计算。例如时间周期是 1 天,那么当天的结果只会在 23:59:59 以后才会生成。 -- 如果有历史记录写入到已经计算完成的时间区间,连续查询并不会重新进行计算,也不会重新将结果推送给用户。对于写回 TDengine 的模式,也不会更新已经存在的计算结果。 -- 使用连续查询推送结果的模式,服务端并不缓存客户端计算状态,也不提供 Exactly-Once 的语义保证。如果用户的应用端崩溃,再次拉起的连续查询将只会从再次拉起的时间开始重新计算最近的一个完整的时间窗口。如果使用写回模式,TDengine 可确保数据写回的有效性和连续性。 - -## 连续查询语法 - -```sql -[CREATE TABLE AS] SELECT select_expr [, select_expr ...] - FROM {tb_name_list} - [WHERE where_condition] - [INTERVAL(interval_val [, interval_offset]) [SLIDING sliding_val]] - -``` - -INTERVAL: 连续查询作用的时间窗口 - -SLIDING: 连续查询的时间窗口向前滑动的时间间隔 - -## 使用连续查询 - -下面以智能电表场景为例介绍连续查询的具体使用方法。假设我们通过下列 SQL 语句创建了超级表和子表: - -```sql -create table meters (ts timestamp, current float, voltage int, phase float) tags (location binary(64), groupId int); -create table D1001 using meters tags ("California.SanFrancisco", 2); -create table D1002 using meters tags ("California.LosAngeles", 2); -... -``` - -可以通过下面这条 SQL 语句以一分钟为时间窗口、30 秒为前向增量统计这些电表的平均电压。 - -```sql -select avg(voltage) from meters interval(1m) sliding(30s); -``` - -每次执行这条语句,都会重新计算所有数据。 如果需要每隔 30 秒执行一次来增量计算最近一分钟的数据,可以把上面的语句改进成下面的样子,每次使用不同的 `startTime` 并定期执行: - -```sql -select avg(voltage) from meters where ts > {startTime} interval(1m) sliding(30s); -``` - -这样做没有问题,但 TDengine 提供了更简单的方法,只要在最初的查询语句前面加上 `create table {tableName} as` 就可以了,例如: - -```sql -create table avg_vol as select avg(voltage) from meters interval(1m) sliding(30s); -``` - -会自动创建一个名为 `avg_vol` 的新表,然后每隔 30 秒,TDengine 会增量执行 `as` 后面的 SQL 语句,并将查询结果写入这个表中,用户程序后续只要从 `avg_vol` 中查询数据即可。例如: - -```sql -taos> select * from avg_vol; - ts | avg_voltage_ | -=================================================== - 2020-07-29 13:37:30.000 | 222.0000000 | - 2020-07-29 13:38:00.000 | 221.3500000 | - 2020-07-29 13:38:30.000 | 220.1700000 | - 2020-07-29 13:39:00.000 | 223.0800000 | -``` - -需要注意,查询时间窗口的最小值是 10 毫秒,没有时间窗口范围的上限。 - -此外,TDengine 还支持用户指定连续查询的起止时间。如果不输入开始时间,连续查询将从第一条原始数据所在的时间窗口开始;如果没有输入结束时间,连续查询将永久运行;如果用户指定了结束时间,连续查询在系统时间达到指定的时间以后停止运行。比如使用下面的 SQL 创建的连续查询将运行一小时,之后会自动停止。 - -```sql -create table avg_vol as select avg(voltage) from meters where ts > now and ts <= now + 1h interval(1m) sliding(30s); -``` - -需要说明的是,上面例子中的 `now` 是指创建连续查询的时间,而不是查询执行的时间,否则,查询就无法自动停止了。另外,为了尽量避免原始数据延迟写入导致的问题,TDengine 中连续查询的计算有一定的延迟。也就是说,一个时间窗口过去后,TDengine 并不会立即计算这个窗口的数据,所以要稍等一会(一般不会超过 1 分钟)才能查到计算结果。 - -## 管理连续查询 - -用户可在控制台中通过 `show streams` 命令来查看系统中全部运行的连续查询,并可以通过 `kill stream` 命令杀掉对应的连续查询。后续版本会提供更细粒度和便捷的连续查询管理命令。 +--- +sidebar_label: 流式计算 +description: "TDengine 流式计算将数据的写入、预处理、复杂分析、实时计算、报警触发等功能融为一体,是一个能够降低用户部署成本、存储成本和运维成本的计算引擎。" +title: 流式计算 +--- +在时序数据的处理中,经常要对原始数据进行清洗、预处理,再使用时序数据库进行长久的储存。用户通常需要在时序数据库之外再搭建 Kafka、Flink、Spark 等流计算处理引擎,增加了用户的开发成本和维护成本。 +使用 TDengine 3.0 的流式计算引擎能够最大限度的减少对这些额外中间件的依赖,真正将数据的写入、预处理、长期存储、复杂分析、实时计算、实时报警触发等功能融为一体,并且,所有这些任务只需要使用 SQL 完成,极大降低了用户的学习成本、使用成本。 + +## 创建流 + +```sql +CREATE STREAM [IF NOT EXISTS] stream_name [stream_options] INTO stb_name AS subquery +stream_options: { + TRIGGER [AT_ONCE | WINDOW_CLOSE | MAX_DELAY time] + WATERMARK time +} + +其中 subquery 是 select 普通查询语法的子集: + +subquery: SELECT [DISTINCT] select_list + from_clause + [WHERE condition] + [PARTITION BY tag_list] + [window_clause] + [group_by_clause] + +不支持 order_by,limit,slimit,fill 语句 +``` +### 触发模式 + +在创建流时,可以通过 TRIGGER 指令指定流式计算的触发模式。 + +对于非窗口计算,流式计算的触发是实时的; + +对于窗口计算,目前提供3种触发模式: + +1. AT_ONCE:写入立即触发 + +2. WINDOW_CLOSE:窗口关闭时触发(窗口关闭由事件时间决定,可配合 WATERMARK 使用,详见《流式计算的乱序数据容忍策略》) + +3. MAX_DELAY time:若窗口关闭,则触发计算。若窗口未关闭,且未关闭时长超过 MAX_DELAY 指定的时间,则触发计算。 + +由于窗口关闭是由事件时间决定的,如事件流中断、或持续延迟,则事件时间无法更新,可能导致无法得到最新的计算结果。 + +因此,流式计算提供了以事件时间结合处理时间计算的 MAX_DELAY 触发模式。 + +MAX_DELAY 模式在窗口关闭时会立即触发计算。此外,当数据写入后,计算触发的时间超过 MAX_DELAY 指定的时间,则立即触发计算。 + +### 乱序数据容忍策略 + +在创建流时,可以在 stream_option 中指定 WATERMARK + +流式计算通过 WATERMARK 来度量对乱序数据的容忍程度,WATERMARK 默认为 0。 + + T = 最新事件时间 - WATERMARK + +每批到来的数据都会以上述公式更新窗口关闭时间,并将窗口结束时间 < T 的所有打开的窗口关闭,若触发模式为 WINDOW_CLOSE 或 MAX_DELAY,则推送窗口聚合结果。 + +### 流式计算的过期数据处理策略 + +对于已关闭的窗口,再次落入该窗口中的数据被标记为过期数据,对于过期数据,流式计算提供两种处理方式: + +1. 直接丢弃:这是常见流式计算引擎提供的默认(甚至是唯一)计算模式 + +2. 重新计算:从 TSDB 中重新查找对应窗口的所有数据并重新计算得到最新结果 + +无论在哪种模式下,watermark 都应该被妥善设置,来得到正确结果(直接丢弃模式)或避免频繁触发重算带来的性能开销(重新计算模式)。 + +### 流式计算与窗口切分查询 + +窗口子句语法如下: + +```sql +window_clause: { + SESSION(ts_col, tol_val) + | STATE_WINDOW(col) + | INTERVAL(interval_val [, interval_offset]) [SLIDING (sliding_val)] +} +``` + +其中,SESSION 是会话窗口,tol_val 是时间间隔的最大范围。在 tol_val 时间间隔范围内的数据都属于同一个窗口,如果连续的两条数据的时间超过 tol_val,则自动开启下一个窗口。 + + +## 流式计算的展示 + +```sql +SHOW STREAMS; +``` + +## 流式计算的删除 + +```sql +DROP STREAM [IF NOT EXISTS] stream_name; +``` + +## 使用案例 + +通过以下案例,进一步了解 TDengine 流计算的使用 + +### 创建 DB 和原始数据表 + +首先准备数据,完成建库、建一张超级表和多张子表操作: + +```sql +drop database if exists stream_db; +create database stream_db; + +create table stream_db.stb (ts timestamp, c1 int, c2 float, c3 varchar(16)) tags(t1 int, t3 varchar(16)); +create table stream_db.ctb0 using stream_db.stb tags(0, "subtable0"); +create table stream_db.ctb1 using stream_db.stb tags(1, "subtable1"); +create table stream_db.ctb2 using stream_db.stb tags(2, "subtable2"); +create table stream_db.ctb3 using stream_db.stb tags(3, "subtable3"); +``` + +### 创建流 +case1. 创建流实现数据过滤 + +```sql +create stream stream1 into stream_db.stream1_output_stb as select * from stream_db.stb where c1 > 0 and c2 != 10 and c3 is not Null; +``` + +case2. 创建流实现标量函数的数据转换 + +```sql +create stream stream2 into stream_db.stream2_output_stb as select ts, abs(c2), char_length(c3), cast(c1 as binary(16)),timezone() from stream_db.stb partition by tbname; +``` + +case3. 创建流实现数据降采样 + +```sql +create stream stream3 into stream_db.stream3_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); +``` + +case4. 通过 trigger window_close 控制流的触发频率 + +```sql +create stream stream4 trigger window_close into stream_db.stream4_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); +``` + +case4. 通过 trigger max_delay 控制流的触发频率 + +```sql +create stream stream5 trigger max_delay 3s into stream_db.stream5_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); +``` + +case6. 通过 watermark 实现乱序数据容忍 + +```sql +create stream stream6 trigger window_close watermark 15s into stream_db.stream6_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); +``` + +case7. 通过 ignore expired 实现乱序数据丢弃 + +```sql +create stream stream7 trigger at_once ignore expired into stream_db.stream7_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); +``` + +### 写入数据 +```sql +insert into stream_db.ctb0 values("2022-08-13 00:00:00", 0, 0, 'a0'); +insert into stream_db.ctb0 values("2022-08-13 00:00:01", 1, 1, 'a1'); +insert into stream_db.ctb0 values("2022-08-13 00:00:07", 7, 7, 'a7'); +insert into stream_db.ctb0 values("2022-08-13 00:00:10", 10, 10, 'a10'); +insert into stream_db.ctb0 values("2022-08-13 00:00:13", 13, 13, 'a13'); +insert into stream_db.ctb0 values("2022-08-13 00:00:20", 20, 20, 'a20'); +insert into stream_db.ctb0 values("2022-08-13 00:00:21", 21, 21, 'a21'); +insert into stream_db.ctb0 values("2022-08-13 00:00:25", 25, 25, 'a25'); +insert into stream_db.ctb0 values("2022-08-13 00:00:26", 26, 26, 'a26'); +``` +### 查询以观查结果 +```sql +select * from stream_db.stream1_output_stb; +select * from stream_db.stream2_output_stb; +select * from stream_db.stream3_output_stb; +select * from stream_db.stream4_output_stb; +select * from stream_db.stream5_output_stb; +select * from stream_db.stream6_output_stb; +select * from stream_db.stream7_output_stb; +``` \ No newline at end of file From 922593bc8f2813425727f78da5201e0c1133a04e Mon Sep 17 00:00:00 2001 From: jiajingbin Date: Mon, 8 Aug 2022 16:12:33 +0800 Subject: [PATCH 2/6] docs: Add 06-stream.md for stream-computing --- docs/zh/07-develop/06-stream.md | 120 ++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/docs/zh/07-develop/06-stream.md b/docs/zh/07-develop/06-stream.md index ca92b9098e..194ed4646c 100644 --- a/docs/zh/07-develop/06-stream.md +++ b/docs/zh/07-develop/06-stream.md @@ -1,8 +1,9 @@ --- sidebar_label: 流式计算 description: "TDengine 流式计算将数据的写入、预处理、复杂分析、实时计算、报警触发等功能融为一体,是一个能够降低用户部署成本、存储成本和运维成本的计算引擎。" -title: 流式计算 +title: 流式计算 --- + 在时序数据的处理中,经常要对原始数据进行清洗、预处理,再使用时序数据库进行长久的储存。用户通常需要在时序数据库之外再搭建 Kafka、Flink、Spark 等流计算处理引擎,增加了用户的开发成本和维护成本。 使用 TDengine 3.0 的流式计算引擎能够最大限度的减少对这些额外中间件的依赖,真正将数据的写入、预处理、长期存储、复杂分析、实时计算、实时报警触发等功能融为一体,并且,所有这些任务只需要使用 SQL 完成,极大降低了用户的学习成本、使用成本。 @@ -13,58 +14,20 @@ CREATE STREAM [IF NOT EXISTS] stream_name [stream_options] INTO stb_name AS subq stream_options: { TRIGGER [AT_ONCE | WINDOW_CLOSE | MAX_DELAY time] WATERMARK time + IGNORE EXPIRED } - + 其中 subquery 是 select 普通查询语法的子集: - + subquery: SELECT [DISTINCT] select_list from_clause [WHERE condition] [PARTITION BY tag_list] [window_clause] [group_by_clause] - + 不支持 order_by,limit,slimit,fill 语句 ``` -### 触发模式 - -在创建流时,可以通过 TRIGGER 指令指定流式计算的触发模式。 - -对于非窗口计算,流式计算的触发是实时的; - -对于窗口计算,目前提供3种触发模式: - -1. AT_ONCE:写入立即触发 - -2. WINDOW_CLOSE:窗口关闭时触发(窗口关闭由事件时间决定,可配合 WATERMARK 使用,详见《流式计算的乱序数据容忍策略》) - -3. MAX_DELAY time:若窗口关闭,则触发计算。若窗口未关闭,且未关闭时长超过 MAX_DELAY 指定的时间,则触发计算。 - -由于窗口关闭是由事件时间决定的,如事件流中断、或持续延迟,则事件时间无法更新,可能导致无法得到最新的计算结果。 - -因此,流式计算提供了以事件时间结合处理时间计算的 MAX_DELAY 触发模式。 - -MAX_DELAY 模式在窗口关闭时会立即触发计算。此外,当数据写入后,计算触发的时间超过 MAX_DELAY 指定的时间,则立即触发计算。 - -### 乱序数据容忍策略 - -在创建流时,可以在 stream_option 中指定 WATERMARK - -流式计算通过 WATERMARK 来度量对乱序数据的容忍程度,WATERMARK 默认为 0。 - - T = 最新事件时间 - WATERMARK - -每批到来的数据都会以上述公式更新窗口关闭时间,并将窗口结束时间 < T 的所有打开的窗口关闭,若触发模式为 WINDOW_CLOSE 或 MAX_DELAY,则推送窗口聚合结果。 - -### 流式计算的过期数据处理策略 - -对于已关闭的窗口,再次落入该窗口中的数据被标记为过期数据,对于过期数据,流式计算提供两种处理方式: - -1. 直接丢弃:这是常见流式计算引擎提供的默认(甚至是唯一)计算模式 - -2. 重新计算:从 TSDB 中重新查找对应窗口的所有数据并重新计算得到最新结果 - -无论在哪种模式下,watermark 都应该被妥善设置,来得到正确结果(直接丢弃模式)或避免频繁触发重算带来的性能开销(重新计算模式)。 ### 流式计算与窗口切分查询 @@ -78,8 +41,55 @@ window_clause: { } ``` -其中,SESSION 是会话窗口,tol_val 是时间间隔的最大范围。在 tol_val 时间间隔范围内的数据都属于同一个窗口,如果连续的两条数据的时间超过 tol_val,则自动开启下一个窗口。 +其中,SESSION 是会话窗口,tol_val 是时间间隔的最大范围。在 tol_val 时间间隔范围内的数据都属于同一个窗口,如果连续的两条数据的时间超过 tol_val,则自动开启下一个窗口; +STATE_WINDOW 是状态窗口,产生的连续记录如果具有相同的状态量数值则归属于同一个状态窗口,数值改变后该窗口关闭; +INTERVAL 是时间窗口,用于产生相等时间周期的窗口,查询过滤、聚合等操作按照每个时间窗口为独立的单位执行; +为了便于理解,下面以 interval(10s) 为例,假设事件时间为 "2022-08-06 00:00:01",对应的窗口为 "2022-08-06 00:00:00" ~ "2022-08-06 00:00:10"(窗口 A),那么下一个窗口为 "2022-08-06 00:00:10" ~ "2022-08-06 00:00:20"(窗口 B)。 + +### 流式计算的触发模式 + +在创建流时,可以通过 TRIGGER 指令指定流式计算的触发模式。 + +对于非窗口计算,流式计算的触发是实时的; + +对于窗口计算,目前提供 3 种触发模式: + +1. AT_ONCE:写入立即触发,窗口 A 和窗口 B 的数据写入后均可以立即触发计算; + +2. WINDOW_CLOSE:窗口关闭时触发(可配合 WATERMARK 使用,详见[流式计算的乱序数据容忍策略](#流式计算的乱序数据容忍策略),当窗口 B 的数据到达时,窗口 A 才会关闭并触发 WINDOW_CLOSE; + +3. MAX_DELAY time:若窗口 A 关闭,则触发计算。若窗口 B 未关闭,且未关闭时长超过 MAX_DELAY 指定的时间,则触发计算。 + +由于窗口关闭是由事件时间决定的,如事件流中断、或持续延迟,则事件时间无法更新,可能导致无法得到最新的计算结果; + +因此,流式计算提供了以事件时间结合处理时间计算的 MAX_DELAY 触发模式; + +MAX_DELAY 模式在窗口关闭时或者数据写入后计算触发的时间超过 MAX_DELAY 指定的时间,会立即触发计算。 + +### 流式计算的乱序数据容忍策略 + +在创建流时,可以在 stream_options 中指定 WATERMARK; + +流式计算通过 WATERMARK 来度量对乱序数据的容忍程度,WATERMARK 默认为 0。 + + T = 最新事件时间 - WATERMARK + +每批到来的数据都会以上述公式更新窗口关闭时间,并将窗口结束时间 < T 的所有打开的窗口关闭,若触发模式为 WINDOW_CLOSE 或 MAX_DELAY,则推送窗口聚合结果。 + +以上面的 WINDOW_CLOSE 为例,如果设置了 WATERMARK 15s,那么窗口 A 和 B 均会延迟推送计算结果,当最新事件时间为 "2022-08-06 00:00:25" 时会推送窗口 A 的结果,当最新事件时间为 "2022-08-06 00:00:35" 时会推送窗口 B 的结果。 + +### 流式计算的过期数据处理策略 + +对于已关闭的窗口,再次落入该窗口中的数据被标记为过期数据,对于过期数据,流式计算提供两种处理方式: + +1. 直接丢弃:这是常见流式计算引擎提供的默认(甚至是唯一)计算模式; + +2. 重新计算:从 TSDB 中重新查找对应窗口的所有数据并重新计算得到最新结果; + +模式 1 创建流时需要在 stream_options 中配置 IGNORE EXPIRED,对于已经关闭的窗口,再次落入该窗口的乱序数据会被直接丢弃; + +无论在哪种模式下,WATERMARK 都应该被妥善设置,来得到正确结果(直接丢弃模式)或避免频繁触发重算带来的性能开销(重新计算模式)。 ## 流式计算的展示 @@ -90,7 +100,7 @@ SHOW STREAMS; ## 流式计算的删除 ```sql -DROP STREAM [IF NOT EXISTS] stream_name; +DROP STREAM [IF EXISTS] stream_name; ``` ## 使用案例 @@ -99,11 +109,11 @@ DROP STREAM [IF NOT EXISTS] stream_name; ### 创建 DB 和原始数据表 -首先准备数据,完成建库、建一张超级表和多张子表操作: +首先准备数据,完成建库、建一张超级表和多张子表操作 ```sql drop database if exists stream_db; -create database stream_db; +create database stream_db; create table stream_db.stb (ts timestamp, c1 int, c2 float, c3 varchar(16)) tags(t1 int, t3 varchar(16)); create table stream_db.ctb0 using stream_db.stb tags(0, "subtable0"); @@ -113,6 +123,7 @@ create table stream_db.ctb3 using stream_db.stb tags(3, "subtable3"); ``` ### 创建流 + case1. 创建流实现数据过滤 ```sql @@ -156,18 +167,21 @@ create stream stream7 trigger at_once ignore expired into stream_db.stream7_outp ``` ### 写入数据 + ```sql -insert into stream_db.ctb0 values("2022-08-13 00:00:00", 0, 0, 'a0'); insert into stream_db.ctb0 values("2022-08-13 00:00:01", 1, 1, 'a1'); insert into stream_db.ctb0 values("2022-08-13 00:00:07", 7, 7, 'a7'); -insert into stream_db.ctb0 values("2022-08-13 00:00:10", 10, 10, 'a10'); -insert into stream_db.ctb0 values("2022-08-13 00:00:13", 13, 13, 'a13'); -insert into stream_db.ctb0 values("2022-08-13 00:00:20", 20, 20, 'a20'); +insert into stream_db.ctb0 values("2022-08-13 00:00:11", 11, 11, 'a11'); insert into stream_db.ctb0 values("2022-08-13 00:00:21", 21, 21, 'a21'); +insert into stream_db.ctb0 values("2022-08-13 00:00:24", 24, 24, 'a24'); insert into stream_db.ctb0 values("2022-08-13 00:00:25", 25, 25, 'a25'); -insert into stream_db.ctb0 values("2022-08-13 00:00:26", 26, 26, 'a26'); +insert into stream_db.ctb0 values("2022-08-13 00:00:34", 34, 34, 'a34'); +insert into stream_db.ctb0 values("2022-08-13 00:00:35", 35, 35, 'a35'); +insert into stream_db.ctb0 values("2022-08-13 00:00:02", 2, 2, 'a2'); ``` + ### 查询以观查结果 + ```sql select * from stream_db.stream1_output_stb; select * from stream_db.stream2_output_stb; @@ -176,4 +190,4 @@ select * from stream_db.stream4_output_stb; select * from stream_db.stream5_output_stb; select * from stream_db.stream6_output_stb; select * from stream_db.stream7_output_stb; -``` \ No newline at end of file +``` From 761149051795a189fdf581c5e768f3d992b1991c Mon Sep 17 00:00:00 2001 From: jiajingbin Date: Mon, 8 Aug 2022 19:31:22 +0800 Subject: [PATCH 3/6] docs: update 06-stream.md for stream-computing --- docs/zh/07-develop/06-stream.md | 228 +++++++++++--------------------- 1 file changed, 80 insertions(+), 148 deletions(-) diff --git a/docs/zh/07-develop/06-stream.md b/docs/zh/07-develop/06-stream.md index 194ed4646c..61f17ff5b4 100644 --- a/docs/zh/07-develop/06-stream.md +++ b/docs/zh/07-develop/06-stream.md @@ -7,7 +7,7 @@ title: 流式计算 在时序数据的处理中,经常要对原始数据进行清洗、预处理,再使用时序数据库进行长久的储存。用户通常需要在时序数据库之外再搭建 Kafka、Flink、Spark 等流计算处理引擎,增加了用户的开发成本和维护成本。 使用 TDengine 3.0 的流式计算引擎能够最大限度的减少对这些额外中间件的依赖,真正将数据的写入、预处理、长期存储、复杂分析、实时计算、实时报警触发等功能融为一体,并且,所有这些任务只需要使用 SQL 完成,极大降低了用户的学习成本、使用成本。 -## 创建流 +## 流式计算的创建 ```sql CREATE STREAM [IF NOT EXISTS] stream_name [stream_options] INTO stb_name AS subquery @@ -16,96 +16,13 @@ stream_options: { WATERMARK time IGNORE EXPIRED } - -其中 subquery 是 select 普通查询语法的子集: - -subquery: SELECT [DISTINCT] select_list - from_clause - [WHERE condition] - [PARTITION BY tag_list] - [window_clause] - [group_by_clause] - -不支持 order_by,limit,slimit,fill 语句 ``` -### 流式计算与窗口切分查询 +详细的语法规则参考 [流式计算](/taos-sql/stream/) -窗口子句语法如下: +## 示例一 -```sql -window_clause: { - SESSION(ts_col, tol_val) - | STATE_WINDOW(col) - | INTERVAL(interval_val [, interval_offset]) [SLIDING (sliding_val)] -} -``` - -其中,SESSION 是会话窗口,tol_val 是时间间隔的最大范围。在 tol_val 时间间隔范围内的数据都属于同一个窗口,如果连续的两条数据的时间超过 tol_val,则自动开启下一个窗口; -STATE_WINDOW 是状态窗口,产生的连续记录如果具有相同的状态量数值则归属于同一个状态窗口,数值改变后该窗口关闭; -INTERVAL 是时间窗口,用于产生相等时间周期的窗口,查询过滤、聚合等操作按照每个时间窗口为独立的单位执行; - -为了便于理解,下面以 interval(10s) 为例,假设事件时间为 "2022-08-06 00:00:01",对应的窗口为 "2022-08-06 00:00:00" ~ "2022-08-06 00:00:10"(窗口 A),那么下一个窗口为 "2022-08-06 00:00:10" ~ "2022-08-06 00:00:20"(窗口 B)。 - -### 流式计算的触发模式 - -在创建流时,可以通过 TRIGGER 指令指定流式计算的触发模式。 - -对于非窗口计算,流式计算的触发是实时的; - -对于窗口计算,目前提供 3 种触发模式: - -1. AT_ONCE:写入立即触发,窗口 A 和窗口 B 的数据写入后均可以立即触发计算; - -2. WINDOW_CLOSE:窗口关闭时触发(可配合 WATERMARK 使用,详见[流式计算的乱序数据容忍策略](#流式计算的乱序数据容忍策略),当窗口 B 的数据到达时,窗口 A 才会关闭并触发 WINDOW_CLOSE; - -3. MAX_DELAY time:若窗口 A 关闭,则触发计算。若窗口 B 未关闭,且未关闭时长超过 MAX_DELAY 指定的时间,则触发计算。 - -由于窗口关闭是由事件时间决定的,如事件流中断、或持续延迟,则事件时间无法更新,可能导致无法得到最新的计算结果; - -因此,流式计算提供了以事件时间结合处理时间计算的 MAX_DELAY 触发模式; - -MAX_DELAY 模式在窗口关闭时或者数据写入后计算触发的时间超过 MAX_DELAY 指定的时间,会立即触发计算。 - -### 流式计算的乱序数据容忍策略 - -在创建流时,可以在 stream_options 中指定 WATERMARK; - -流式计算通过 WATERMARK 来度量对乱序数据的容忍程度,WATERMARK 默认为 0。 - - T = 最新事件时间 - WATERMARK - -每批到来的数据都会以上述公式更新窗口关闭时间,并将窗口结束时间 < T 的所有打开的窗口关闭,若触发模式为 WINDOW_CLOSE 或 MAX_DELAY,则推送窗口聚合结果。 - -以上面的 WINDOW_CLOSE 为例,如果设置了 WATERMARK 15s,那么窗口 A 和 B 均会延迟推送计算结果,当最新事件时间为 "2022-08-06 00:00:25" 时会推送窗口 A 的结果,当最新事件时间为 "2022-08-06 00:00:35" 时会推送窗口 B 的结果。 - -### 流式计算的过期数据处理策略 - -对于已关闭的窗口,再次落入该窗口中的数据被标记为过期数据,对于过期数据,流式计算提供两种处理方式: - -1. 直接丢弃:这是常见流式计算引擎提供的默认(甚至是唯一)计算模式; - -2. 重新计算:从 TSDB 中重新查找对应窗口的所有数据并重新计算得到最新结果; - -模式 1 创建流时需要在 stream_options 中配置 IGNORE EXPIRED,对于已经关闭的窗口,再次落入该窗口的乱序数据会被直接丢弃; - -无论在哪种模式下,WATERMARK 都应该被妥善设置,来得到正确结果(直接丢弃模式)或避免频繁触发重算带来的性能开销(重新计算模式)。 - -## 流式计算的展示 - -```sql -SHOW STREAMS; -``` - -## 流式计算的删除 - -```sql -DROP STREAM [IF EXISTS] stream_name; -``` - -## 使用案例 - -通过以下案例,进一步了解 TDengine 流计算的使用 +查找过去 12 小时电表电压大于 220V 的记录条数和电流的最大值,并对采集的数据按时间窗口聚合。 ### 创建 DB 和原始数据表 @@ -115,79 +32,94 @@ DROP STREAM [IF EXISTS] stream_name; drop database if exists stream_db; create database stream_db; -create table stream_db.stb (ts timestamp, c1 int, c2 float, c3 varchar(16)) tags(t1 int, t3 varchar(16)); -create table stream_db.ctb0 using stream_db.stb tags(0, "subtable0"); -create table stream_db.ctb1 using stream_db.stb tags(1, "subtable1"); -create table stream_db.ctb2 using stream_db.stb tags(2, "subtable2"); -create table stream_db.ctb3 using stream_db.stb tags(3, "subtable3"); +create stable stream_db.meters (ts timestamp, current float, voltage int) TAGS (location varchar(64), groupId int); + +create table stream_db.d1001 using stream_db.meters tags("beijing", 1); +create table stream_db.d1002 using stream_db.meters tags("guangzhou", 2); +create table stream_db.d1003 using stream_db.meters tags("shanghai", 3); ``` ### 创建流 -case1. 创建流实现数据过滤 - ```sql -create stream stream1 into stream_db.stream1_output_stb as select * from stream_db.stb where c1 > 0 and c2 != 10 and c3 is not Null; -``` - -case2. 创建流实现标量函数的数据转换 - -```sql -create stream stream2 into stream_db.stream2_output_stb as select ts, abs(c2), char_length(c3), cast(c1 as binary(16)),timezone() from stream_db.stb partition by tbname; -``` - -case3. 创建流实现数据降采样 - -```sql -create stream stream3 into stream_db.stream3_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); -``` - -case4. 通过 trigger window_close 控制流的触发频率 - -```sql -create stream stream4 trigger window_close into stream_db.stream4_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); -``` - -case4. 通过 trigger max_delay 控制流的触发频率 - -```sql -create stream stream5 trigger max_delay 3s into stream_db.stream5_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); -``` - -case6. 通过 watermark 实现乱序数据容忍 - -```sql -create stream stream6 trigger window_close watermark 15s into stream_db.stream6_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); -``` - -case7. 通过 ignore expired 实现乱序数据丢弃 - -```sql -create stream stream7 trigger at_once ignore expired into stream_db.stream7_output_stb as select _wstart as start, min(c1), max(c2), count(c3) from stream_db.stb interval (10s); +create stream stream1 into stream_db.stream1_output_stb as select _wstart as start, count(voltage), max(current) from stream_db.meters where voltage > 220 and ts > now - 12h interval (1h); ``` ### 写入数据 - ```sql -insert into stream_db.ctb0 values("2022-08-13 00:00:01", 1, 1, 'a1'); -insert into stream_db.ctb0 values("2022-08-13 00:00:07", 7, 7, 'a7'); -insert into stream_db.ctb0 values("2022-08-13 00:00:11", 11, 11, 'a11'); -insert into stream_db.ctb0 values("2022-08-13 00:00:21", 21, 21, 'a21'); -insert into stream_db.ctb0 values("2022-08-13 00:00:24", 24, 24, 'a24'); -insert into stream_db.ctb0 values("2022-08-13 00:00:25", 25, 25, 'a25'); -insert into stream_db.ctb0 values("2022-08-13 00:00:34", 34, 34, 'a34'); -insert into stream_db.ctb0 values("2022-08-13 00:00:35", 35, 35, 'a35'); -insert into stream_db.ctb0 values("2022-08-13 00:00:02", 2, 2, 'a2'); +insert into stream_db.d1001 values(now-14h, 10.3, 210); +insert into stream_db.d1001 values(now-13h, 13.5, 226); +insert into stream_db.d1001 values(now-12h, 12.5, 221); +insert into stream_db.d1002 values(now-11h, 14.7, 221); +insert into stream_db.d1002 values(now-10h, 10.5, 219); +insert into stream_db.d1002 values(now-9h, 11.2, 217); +insert into stream_db.d1003 values(now-8h, 11.5, 222); +insert into stream_db.d1003 values(now-7h, 12.3, 227); +insert into stream_db.d1003 values(now-6h, 12.3, 215); ``` ### 查询以观查结果 +```sql +taos> select * from stream_db.stream1_output_stb; + start | count(voltage) | max(current) | group_id | +================================================================================================= + 2022-08-08 08:00:00.000 | 1 | 14.70000 | 0 | + 2022-08-08 11:00:00.000 | 1 | 11.50000 | 0 | + 2022-08-08 12:00:00.000 | 1 | 12.30000 | 0 | +Query OK, 3 rows in database (0.008239s) +``` + +## 示例二 +查询所有电表中电压等于 220V 的数据,对过滤出的电表电流数据进行四舍五入运算,同时将主键时间戳列转换为 bigint 类型,并对采集的数据按表名分组。 + +### 创建 DB 和原始数据表 +首先准备数据,完成建库、建一张超级表和多张子表操作 ```sql -select * from stream_db.stream1_output_stb; -select * from stream_db.stream2_output_stb; -select * from stream_db.stream3_output_stb; -select * from stream_db.stream4_output_stb; -select * from stream_db.stream5_output_stb; -select * from stream_db.stream6_output_stb; -select * from stream_db.stream7_output_stb; +drop database if exists stream_db; +create database stream_db; + +create stable stream_db.meters (ts timestamp, current float, voltage int) TAGS (location varchar(64), groupId int); + +create table stream_db.d1001 using stream_db.meters tags("beijing", 1); +create table stream_db.d1002 using stream_db.meters tags("shanghai", 2); +create table stream_db.d1003 using stream_db.meters tags("beijing", 2); +create table stream_db.d1004 using stream_db.meters tags("tianjin", 3); +create table stream_db.d1005 using stream_db.meters tags("shanghai", 1); ``` + +### 创建流 + +```sql +create stream stream2 into stream_db.stream2_output_stb as select ts,cast(ts as bigint),round(current),location from stream_db.meters where voltage=220 partition by tbname; +``` + +### 写入数据 +```sql +insert into stream_db.d1001 values(now-14h, 10.3, 210); +insert into stream_db.d1001 values(now-13h, 13.5, 220); +insert into stream_db.d1002 values(now-12h, 14.7, 218); +insert into stream_db.d1002 values(now-11h, 10.5, 220); +insert into stream_db.d1003 values(now-10h, 11.5, 220); +insert into stream_db.d1003 values(now-9h, 12.3, 215); +insert into stream_db.d1004 values(now-8h, 11.5, 220); +insert into stream_db.d1004 values(now-7h, 15.3, 217); +insert into stream_db.d1005 values(now-6h, 16.5, 216); +insert into stream_db.d1005 values(now-5h, 12.3, 220); +``` +### 查询以观查结果 +```sql +taos> select * from stream_db.stream2_output_stb; + ts | cast(ts as bigint) | round(current) | location | group_id | +================================================================================================================================== + 2022-08-08 09:29:55.557 | 1659922195557 | 12.00000 | beijing | 7226905450883977166 | + 2022-08-08 11:29:55.570 | 1659929395570 | 12.00000 | tianjin | 7226905450884501455 | + 2022-08-08 08:29:55.549 | 1659918595549 | 11.00000 | shanghai | 7226905450883452877 | + 2022-08-08 06:29:55.534 | 1659911395534 | 14.00000 | beijing | 7226905450882928588 | + 2022-08-08 14:29:56.175 | 1659940196175 | 12.00000 | shanghai | 7226905450895708112 | +Query OK, 5 rows in database (0.015235s) +``` + +## 示例三 +...待续 + From 88555c21201fdb224b895c244bec1bc87ad5b1e3 Mon Sep 17 00:00:00 2001 From: jiajingbin Date: Tue, 9 Aug 2022 09:03:51 +0800 Subject: [PATCH 4/6] docs: update 06-stream.md for stream-computing --- docs/zh/07-develop/06-stream.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/07-develop/06-stream.md b/docs/zh/07-develop/06-stream.md index 61f17ff5b4..78f1edbfe8 100644 --- a/docs/zh/07-develop/06-stream.md +++ b/docs/zh/07-develop/06-stream.md @@ -18,7 +18,7 @@ stream_options: { } ``` -详细的语法规则参考 [流式计算](/taos-sql/stream/) +详细的语法规则参考 [流式计算](../../taos-sql/stream) ## 示例一 From 06aac0fca1b05de89a6e66b5bb961a801b8e941c Mon Sep 17 00:00:00 2001 From: jiajingbin Date: Wed, 10 Aug 2022 00:37:34 +0800 Subject: [PATCH 5/6] docs: update 06-stream.md for stream-computing --- docs/zh/07-develop/06-stream.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/zh/07-develop/06-stream.md b/docs/zh/07-develop/06-stream.md index 78f1edbfe8..ee5969143a 100644 --- a/docs/zh/07-develop/06-stream.md +++ b/docs/zh/07-develop/06-stream.md @@ -22,7 +22,7 @@ stream_options: { ## 示例一 -查找过去 12 小时电表电压大于 220V 的记录条数和电流的最大值,并对采集的数据按时间窗口聚合。 +企业电表的数据经常都是成百上千亿条的,那么想要将这些分散、凌乱的数据清洗或转换都需要比较长的时间,很难做到高效性和实时性,以下例子中,通过流计算可以将过去 12 小时电表电压大于 220V 的数据清洗掉,然后以小时为窗口整合并计算出每个窗口中电流的最大值,并将结果输出到指定的数据表中。 ### 创建 DB 和原始数据表 @@ -42,18 +42,18 @@ create table stream_db.d1003 using stream_db.meters tags("shanghai", 3); ### 创建流 ```sql -create stream stream1 into stream_db.stream1_output_stb as select _wstart as start, count(voltage), max(current) from stream_db.meters where voltage > 220 and ts > now - 12h interval (1h); +create stream stream1 into stream_db.stream1_output_stb as select _wstart as start, _wend as end, max(current) as max_current from stream_db.meters where voltage <= 220 and ts > now - 12h interval (1h); ``` ### 写入数据 ```sql insert into stream_db.d1001 values(now-14h, 10.3, 210); -insert into stream_db.d1001 values(now-13h, 13.5, 226); -insert into stream_db.d1001 values(now-12h, 12.5, 221); +insert into stream_db.d1001 values(now-13h, 13.5, 216); +insert into stream_db.d1001 values(now-12h, 12.5, 219); insert into stream_db.d1002 values(now-11h, 14.7, 221); -insert into stream_db.d1002 values(now-10h, 10.5, 219); -insert into stream_db.d1002 values(now-9h, 11.2, 217); -insert into stream_db.d1003 values(now-8h, 11.5, 222); +insert into stream_db.d1002 values(now-10h, 10.5, 218); +insert into stream_db.d1002 values(now-9h, 11.2, 220); +insert into stream_db.d1003 values(now-8h, 11.5, 217); insert into stream_db.d1003 values(now-7h, 12.3, 227); insert into stream_db.d1003 values(now-6h, 12.3, 215); ``` @@ -61,12 +61,13 @@ insert into stream_db.d1003 values(now-6h, 12.3, 215); ### 查询以观查结果 ```sql taos> select * from stream_db.stream1_output_stb; - start | count(voltage) | max(current) | group_id | -================================================================================================= - 2022-08-08 08:00:00.000 | 1 | 14.70000 | 0 | - 2022-08-08 11:00:00.000 | 1 | 11.50000 | 0 | - 2022-08-08 12:00:00.000 | 1 | 12.30000 | 0 | -Query OK, 3 rows in database (0.008239s) + start | end | max_current | group_id | +=================================================================================================== + 2022-08-09 14:00:00.000 | 2022-08-09 15:00:00.000 | 10.50000 | 0 | + 2022-08-09 15:00:00.000 | 2022-08-09 16:00:00.000 | 11.20000 | 0 | + 2022-08-09 16:00:00.000 | 2022-08-09 17:00:00.000 | 11.50000 | 0 | + 2022-08-09 18:00:00.000 | 2022-08-09 19:00:00.000 | 12.30000 | 0 | +Query OK, 4 rows in database (0.012033s) ``` ## 示例二 From 8a9e3ed76cb62b08c44801f2d5e64d28f02ef86d Mon Sep 17 00:00:00 2001 From: jiajingbin Date: Wed, 10 Aug 2022 11:30:04 +0800 Subject: [PATCH 6/6] docs: update 06-stream.md for stream-computing --- docs/zh/07-develop/06-stream.md | 62 +++++++++++++++++---------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/docs/zh/07-develop/06-stream.md b/docs/zh/07-develop/06-stream.md index ee5969143a..dbf8684008 100644 --- a/docs/zh/07-develop/06-stream.md +++ b/docs/zh/07-develop/06-stream.md @@ -71,7 +71,7 @@ Query OK, 4 rows in database (0.012033s) ``` ## 示例二 -查询所有电表中电压等于 220V 的数据,对过滤出的电表电流数据进行四舍五入运算,同时将主键时间戳列转换为 bigint 类型,并对采集的数据按表名分组。 +某运营商平台要采集机房所有服务器的系统资源指标,包含 cpu、内存、网络延迟等,采集后需要对数据进行四舍五入运算,将地域和服务器名以下划线拼接,然后将结果按时间排序并以服务器名分组输出到新的数据表中。 ### 创建 DB 和原始数据表 首先准备数据,完成建库、建一张超级表和多张子表操作 @@ -80,47 +80,49 @@ Query OK, 4 rows in database (0.012033s) drop database if exists stream_db; create database stream_db; -create stable stream_db.meters (ts timestamp, current float, voltage int) TAGS (location varchar(64), groupId int); +create stable stream_db.idc (ts timestamp, cpu float, mem float, latency float) TAGS (location varchar(64), groupId int); -create table stream_db.d1001 using stream_db.meters tags("beijing", 1); -create table stream_db.d1002 using stream_db.meters tags("shanghai", 2); -create table stream_db.d1003 using stream_db.meters tags("beijing", 2); -create table stream_db.d1004 using stream_db.meters tags("tianjin", 3); -create table stream_db.d1005 using stream_db.meters tags("shanghai", 1); +create table stream_db.server01 using stream_db.idc tags("beijing", 1); +create table stream_db.server02 using stream_db.idc tags("shanghai", 2); +create table stream_db.server03 using stream_db.idc tags("beijing", 2); +create table stream_db.server04 using stream_db.idc tags("tianjin", 3); +create table stream_db.server05 using stream_db.idc tags("shanghai", 1); ``` ### 创建流 ```sql -create stream stream2 into stream_db.stream2_output_stb as select ts,cast(ts as bigint),round(current),location from stream_db.meters where voltage=220 partition by tbname; +create stream stream2 into stream_db.stream2_output_stb as select ts, concat_ws("_", location, tbname) as server_location, round(cpu) as cpu, round(mem) as mem, round(latency) as latency from stream_db.idc partition by tbname order by ts; ``` ### 写入数据 ```sql -insert into stream_db.d1001 values(now-14h, 10.3, 210); -insert into stream_db.d1001 values(now-13h, 13.5, 220); -insert into stream_db.d1002 values(now-12h, 14.7, 218); -insert into stream_db.d1002 values(now-11h, 10.5, 220); -insert into stream_db.d1003 values(now-10h, 11.5, 220); -insert into stream_db.d1003 values(now-9h, 12.3, 215); -insert into stream_db.d1004 values(now-8h, 11.5, 220); -insert into stream_db.d1004 values(now-7h, 15.3, 217); -insert into stream_db.d1005 values(now-6h, 16.5, 216); -insert into stream_db.d1005 values(now-5h, 12.3, 220); +insert into stream_db.server01 values(now-14h, 50.9, 654.8, 23.11); +insert into stream_db.server01 values(now-13h, 13.5, 221.2, 11.22); +insert into stream_db.server02 values(now-12h, 154.7, 218.3, 22.33); +insert into stream_db.server02 values(now-11h, 120.5, 111.5, 5.55); +insert into stream_db.server03 values(now-10h, 101.5, 125.6, 5.99); +insert into stream_db.server03 values(now-9h, 12.3, 165.6, 6.02); +insert into stream_db.server04 values(now-8h, 160.9, 120.7, 43.51); +insert into stream_db.server04 values(now-7h, 240.9, 520.7, 54.55); +insert into stream_db.server05 values(now-6h, 190.9, 320.7, 55.43); +insert into stream_db.server05 values(now-5h, 110.9, 600.7, 35.54); ``` ### 查询以观查结果 ```sql -taos> select * from stream_db.stream2_output_stb; - ts | cast(ts as bigint) | round(current) | location | group_id | -================================================================================================================================== - 2022-08-08 09:29:55.557 | 1659922195557 | 12.00000 | beijing | 7226905450883977166 | - 2022-08-08 11:29:55.570 | 1659929395570 | 12.00000 | tianjin | 7226905450884501455 | - 2022-08-08 08:29:55.549 | 1659918595549 | 11.00000 | shanghai | 7226905450883452877 | - 2022-08-08 06:29:55.534 | 1659911395534 | 14.00000 | beijing | 7226905450882928588 | - 2022-08-08 14:29:56.175 | 1659940196175 | 12.00000 | shanghai | 7226905450895708112 | -Query OK, 5 rows in database (0.015235s) +taos> select ts, server_location, cpu, mem, latency from stream_db.stream2_output_stb; + ts | server_location | cpu | mem | latency | +================================================================================================================================ + 2022-08-09 21:24:56.785 | beijing_server01 | 51.00000 | 655.00000 | 23.00000 | + 2022-08-09 22:24:56.795 | beijing_server01 | 14.00000 | 221.00000 | 11.00000 | + 2022-08-09 23:24:56.806 | shanghai_server02 | 155.00000 | 218.00000 | 22.00000 | + 2022-08-10 00:24:56.815 | shanghai_server02 | 121.00000 | 112.00000 | 6.00000 | + 2022-08-10 01:24:56.826 | beijing_server03 | 102.00000 | 126.00000 | 6.00000 | + 2022-08-10 02:24:56.838 | beijing_server03 | 12.00000 | 166.00000 | 6.00000 | + 2022-08-10 03:24:56.846 | tianjin_server04 | 161.00000 | 121.00000 | 44.00000 | + 2022-08-10 04:24:56.853 | tianjin_server04 | 241.00000 | 521.00000 | 55.00000 | + 2022-08-10 05:24:56.866 | shanghai_server05 | 191.00000 | 321.00000 | 55.00000 | + 2022-08-10 06:24:57.301 | shanghai_server05 | 111.00000 | 601.00000 | 36.00000 | +Query OK, 10 rows in database (0.022950s) ``` -## 示例三 -...待续 -