InfluxDB 到底在存什么?从 Measurement、Tag、Field 到 Series
假设要做一套服务器监控,每隔 10 秒记录一次 CPU 使用率:
1 | 时间:2026-08-26 17:50:00 |
放进 MySQL 没什么障碍:建表、插入、给时间和主机名加索引。但监控系统接下来会问:
web-01最近一小时的 CPU 使用率怎么变化?- 每台主机每 5 分钟的平均值是多少?
- 哪些主机刚刚出现异常尖峰?
- 30 天前的秒级数据是否还要保留?
这些数据总在沿时间范围被读取、分组和聚合。InfluxDB 的数据模型也围绕这种访问方式展开。
InfluxDB 3 同样有表和列,也能用 SQL 查询。只看一行数据,它和普通关系表很像;把时间范围查询、持续写入、窗口聚合和数据保留放在一起,设计取向才显出来。关系型数据库需要照顾事务更新和复杂关联,时序数据库首先照顾一连串观测值。
可以把每次采集理解成一句话:某个对象,在某个时间点,产生了一组测量结果。后面的概念都在给这句话里的成分命名。
这一篇先回答一个基础问题:
一条监控数据写进 InfluxDB 后,到底变成了什么?
本文以 InfluxDB 3 Core 为主。InfluxDB 1.x、2.x 和 3.x 的存储容器、查询语言与部分实现不同,涉及版本差异时会单独说明。
一条观测数据,就是一个 Point
前面的 CPU 数据写成 Line Protocol,大致是这样:
1 | cpu,host=web-01,region=cn-north usage_user=23.4,usage_system=8.1 <timestamp> |
先不管语法,按内容拆开:
1 | cpu |
这是一个 Point:某个对象在某个时间点产生的一组测量结果。它有四个部分:
| 部分 | 这个例子里的值 | 回答的问题 |
|---|---|---|
| Measurement / Table | cpu | 测量什么 |
| Tag Set | host=web-01,region=cn-north | 数据来自哪里、属于谁 |
| Field Set | usage_user=23.4,usage_system=8.1 | 测到了什么值 |
| Timestamp | <timestamp> | 什么时候发生 |
Point 会被写进某个 Database。Database 是外层逻辑容器,由写入请求指定,不出现在这行 Line Protocol 中。
1 | Database |
这个层次和“库、表、行、列”有相似之处,不能完全按关系表套用。Tag 会参与 Series 和主键的构成,Field 则保存观测值;两者在表里看着都是列,设计职责并不相同。
Measurement / Table:测量什么
Line Protocol 把 cpu 称为 Measurement。InfluxDB 3 的数据模型和 SQL 文档更常称它为 Table:
1 | SELECT * FROM cpu; |
名称用来描述测量对象,cpu、memory、disk_io、weather、http_request 都很自然。主机归属交给 Tag,不必按来源拆表:
1 | cpu,host=web-01 ... |
cpu_web_01、cpu_web_02 会把同类数据拆散。主机一多,查询所有机器就得访问一批结构相同的表,新增机器还会继续制造表。
cpu_2026_08_26 这类名字也在重复 Timestamp 已经承担的工作。日期变化时,连续的时间线被人为切开,跨日期查询平白多出一层表名处理。
Timestamp:落在时间轴的哪里
Timestamp 是 Unix 时间戳。写入可以采用秒、毫秒、微秒或纳秒精度,客户端与服务端必须使用同一种解释。
例如 1724659200,按秒解释是正常日期,按纳秒解释会落到 1970 年附近。写入可能照样成功,只是查询最近一小时找不到它。
省略 Timestamp 时,InfluxDB 使用服务器接收数据时的当前 UTC 时间。手工测试可以这样做,采集系统则应优先提交事件实际发生的时间。传感器断网后补写时,接收时间和事件时间往往差得很远。
时间精度应在写入接口、客户端配置和实际数值之间统一。排查“写成功但查不到”时,先扩大时间范围并检查时间戳精度,通常比反复改 SQL 更有效。
Tag 和 Field 怎么分
这两个概念最容易把 Schema 带偏。
host=web-01、region=cn-north 是 Tag。它们描述 Point 的上下文,常用于定位、筛选和分组。主机名、机房、服务名、环境名、传感器编号通常都从这里考虑。
例如“只看 web-01”“比较各区域”“按服务分组”都要依赖 Tag:
1 | SELECT time, usage_user |
usage_user=23.4、usage_system=8.1 是 Field。它们保存随时间变化的测量值,通常会参与平均值、最大值、分位数等计算。温度、请求耗时、队列长度、检查结果也属于这类候选值。
一个 Point 至少要有一个 Field,也可以同时写多个 Field。温度和湿度来自同一个传感器、发生在同一时间,放在一个 Point 里就很合适:
1 | weather,city=Beijing,sensor=s01 temperature=26.5,humidity=58i <timestamp> |
InfluxDB 3 会把 Field 表现为有类型的表列。Line Protocol 支持浮点数、有符号整数、无符号整数、字符串和布尔值;同一列的类型应保持一致。
Tag 的值也是字符串,但它在模型中承担另一种职责。在 InfluxDB 3 Core 里,Tag 列和时间列共同构成表的主键,Tag 的顺序还会成为数据的物理排序依据。把一列设为 Tag,会同时影响数据组织和查询。
判断时先问它会怎样被使用:
| 数据 | 更可能是什么 | 原因 |
|---|---|---|
host=web-01 | Tag | 经常按主机筛选或分组 |
region=cn-north | Tag | 经常比较不同区域 |
temperature=26.5 | Field | 随时间变化的测量值 |
response_time=83 | Field | 用来计算平均值、分位数或最大值 |
environment=production | Tag | 常用作过滤条件 |
| 一大段错误堆栈 | Field,或者放进日志系统 | 不适合进入主键,也未必适合时序数据库 |
status 没有固定答案。经常查“所有 offline 设备”,它适合做 Tag;只想保留每次检查结果,很少按状态筛选,做 Field 更省事。
数据是字符串还是数字,只能决定它能否以某种类型存储,不能替你决定 Tag 和 Field。
一个实用的检查方法是把候选列放进查询里试读:它经常出现在 WHERE、GROUP BY 中,Tag 的理由就比较充分;主要出现在 AVG、MAX、SUM 等计算中,通常留作 Field。两边都用得到时,再结合主键宽度、基数和实际查询频率做取舍。
Series、重复点和 Cardinality
先省略 Field 与 Timestamp,看几条写入:
1 | cpu,host=web-01,region=cn-north ... |
Measurement 与完整 Tag Set 相同的 Point 属于同一条 Series。所以上面有四条:
1 | cpu,host=web-01,region=cn-north |
开头两条 web-01 数据的 Measurement 和 Tag Set 完全相同,只是时间与 Field Value 不同,因此落在同一条 Series。web-02、cn-south 或 memory 任意一项发生变化,都会换到另一条 Series。
Field 不参与 Series 身份计算。下面两条数据记录了不同的 Field,仍在同一条 Series 上:
1 | cpu,host=web-01 usage_user=23.4 <timestamp-1> |
1 | cpu,host=web-01,region=cn-north |
每个圆点是一个 Point;Measurement 和 Tag Set 确定它在哪条线上,Timestamp 确定它在线上的位置。
相同时间写两次会怎样
在 InfluxDB 3 Core 中,一个 Point 的逻辑身份可以写成:
1 | Table + 完整 Tag Set + Timestamp |
Field Set 携带数值,不负责唯一性。因此,增加一个 Field 不能制造“新版本”。
例如下面两次写入拥有相同的 Table、Tag Set 和 Timestamp,只是 usage_user 冲突:
1 | cpu,host=web-01 usage_user=23.4 <same-timestamp> |
重复写入中不重名的 Field 会按并集合并;麻烦出在同名 Field。InfluxDB 2.x 的 TSM 存储引擎对这类冲突有明确的覆盖规则,InfluxDB 3 Core 则不同:官方文档特别提醒,冲突值的覆盖结果不确定,查询可能看到任一版本,最终持久化哪一个也没有保证。
工程上应把 InfluxDB 3 Core 当作追加写入系统,别用重复点模拟可靠的 UPDATE 或 UPSERT:
- 写入真实事件时间;
- 两次事件都要保留时,使用不同的 Timestamp;
- 重试前弄清重复写入会不会撞上相同主键。
如果业务确实要表达修订记录,应给修订事件独立的时间或重新设计可区分它们的主键。往 Field 里增加 revision=2 没用,因为 Field 不参与 Point 的身份计算。
Cardinality 数的是 Tag 组合
Series Cardinality 可以粗略理解为系统中不同 Measurement 与 Tag Set 组合的数量。
假设 cpu 有两个 Tag:
1 | host = 1000 种可能值 |
如果每台主机只属于一个区域,实际 Cardinality 大约是 1000,不是把两列取值数相乘得到 5000。它统计已经出现的组合。加入 environment 后,同一主机分别出现在生产和测试环境,才会继续产生新 Series。
所以要估算的是 Tag 将形成多少种组合,单看 Tag 列数没有用。
这个区别在评审 Schema 时很实用。region 有 5 个值,不代表它必然把 Series 数量放大 5 倍;每台主机固定属于一个区域时,host 和 region 存在关联。反过来,如果每个请求都产生一个全新的 request_id,即使只有这一个 Tag,Series 也可能按请求数增长。
InfluxDB 2 和 3 对高基数的态度不同
InfluxDB 1.x、2.x 的 TSM/TSI 架构中,Series 索引会消耗大量资源。UUID、请求 ID、用户 ID 这类高变化值进入 Tag 后,可能快速推高 Cardinality,增加内存占用并拖慢读写。“高基数值放 Field”对 InfluxDB 2.x 仍是有现实意义的经验。
InfluxDB 3 使用了不同的存储和查询架构,官方明确把高 Series Cardinality 列为可处理场景。设备 ID、传感器 ID,甚至某些请求 ID 或跟踪 ID,如果确实需要用来查询,可以设计成 Tag。
“确实需要”指的是明确的查询路径,例如经常按 device_id 拉取单台设备的历史,或按 trace_id 定位一次调用。只是为了把原始数据完整搬进来,不足以支持这个决定。
但不能因此把每一列都改成 Tag。成本依然摆在那里:
- Tag 越多,主键越宽;
- 首次写入时的 Tag 顺序会影响 InfluxDB 3 的物理排序;
- 长随机字符串仍需存储和比较;
- 大量稀疏列会增加表的管理成本;
- 从不用于筛选和分组的值,放进 Tag 没有查询收益。
InfluxDB 2 需要格外警惕 Series Cardinality;InfluxDB 3 放宽了这项约束,但 Tag 依然要服务查询。
先写查询问题,再写 Schema
照着现有 JSON 原样落库很省事:字符串做 Tag,数字做 Field,写入成功就算完成。麻烦会留给查询。
假设采集程序拿到:
1 | { |
先列出准备回答的问题:
- 是否需要按
host查看趋势? - 是否需要比较不同
region? - 是否会按采集器版本筛选故障主机?
usage_user和usage_system要做哪些聚合?
前两个查询经常出现,host 和 region 就适合做 Tag。usage_user、usage_system 是聚合对象,做 Field。
collector_version 取决于使用方式。只用于偶尔展示,可以做 String Field:
1 | cpu,host=web-01,region=cn-north usage_user=23.4,usage_system=8.1,collector_version="1.7.0" <timestamp> |
经常需要筛选“所有 1.7.0 版本的采集器”,则可以放进 Tag:
1 | cpu,host=web-01,region=cn-north,collector_version=1.7.0 usage_user=23.4,usage_system=8.1 <timestamp> |
同一份原始数据,由于查询意图不同,会得到不同的 Schema。
这也是 Schema 需要尽早讨论的原因。数据写进去以后再把 Field 改成 Tag,往往涉及新列、新写入格式和历史数据迁移,远不止给列换一个标签。
文章开头那句“过去一小时,每台主机每 5 分钟的平均 CPU 用户态使用率”,已经把四类信息都点出来了:
1 | Table → cpu |
对应的 SQL 大致如下:
1 | SELECT |
这条 SQL 也能反过来审查设计:若 host 当初只是 String Field,按主机筛选和分组就没有利用 Tag 的数据组织方式;若 usage_user 被设计成 Tag,数值聚合的职责又放错了位置。
写入侧也随之明确:采集程序提交真实事件时间,高频 Point 批量发送,Field 类型尽早稳定;重试不能默认等同于一次幂等更新。
1 | 17:50:00 CPU 使用率 23.4% |
这条时间线记录的是变化过程。新 Point 通常接在后面,旧 Point 仍然保留;保留多久、是否降采样,再交给数据生命周期策略处理。
从查询反推还有一层好处:它会暴露选库问题。复杂事务、频繁多表关联适合留在关系型数据库,大段日志的全文检索适合日志系统。数据带时间,只说明它有时间属性,并不能自动说明它适合 InfluxDB。
把这条数据读完整
1 | cpu,host=web-01,region=cn-north usage_user=23.4,usage_system=8.1 <timestamp> |
cpu 是 Measurement,在 InfluxDB 3 中对应 Table;host 和 region 是 Tag;两个使用率是 Field;Timestamp 给出观测发生时间。Measurement 与 Tag Set 相同的 Point 连成 Series,完整 Tag Set 与 Timestamp 又确定单个 Point 的逻辑身份。
1 | Point = Measurement + Tag Set + Field Set + Timestamp |
Schema 不该从 JSON 的数据类型照抄出来。先写出系统要回答的问题,再决定哪些上下文值得进入 Tag、哪些测量值留在 Field,这套模型才会在查询时顺手。
下一篇继续拆 Line Protocol,并把数据真正写进 InfluxDB 3 Core。
参考资料
- InfluxDB 3 Core Documentation: Get started
- InfluxDB 3 Core Documentation: Line protocol
- InfluxDB 3 Core Documentation: Schema design
- InfluxDB 3 Core Documentation: Glossary
- InfluxDB 3 Core Documentation: Query using SQL
- InfluxDB 2 Documentation: InfluxDB data elements
- InfluxDB 2 Documentation: Resolve high series cardinality
- 标题: InfluxDB 到底在存什么?从 Measurement、Tag、Field 到 Series
- 作者: Kaku
- 创建于 : 2026-08-26 17:50:00
- 更新于 : 2026-08-26 18:33:46
- 链接: https://www.kakunet.top/2026/08/26/InfluxDB-到底在存什么?从-Measurement、Tag、Field-到-Series/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。