历史数据存储
保存变量历史,用于查询过去的值和趋势。第一次练习可使用 SQLite 文件,不需要另外安装数据库服务器。
先把一个值存到本机
- 打开“开发配置 → 数据转发”,新增转发组
Demo_History_Group。 - 变量范围选择“手动选择”,触发模式选择“定时”,定时间隔填
1000,保存。 - 点击组名称右侧的编辑图标,在弹窗下方“转发变量”中搜索一个正常采集的变量,选中后点击加号。确认表格出现该变量,再点击底部“保存”。可以使用上一课的
Demo_Modbus_Device.Demo_Modbus_Value。 - 选中这个组,新增目标
Demo_History_Target,插件选择“历史数据存储”。 - 切换到“插件属性”页签,按下表填写,其他配置保持默认。
| 配置项 | 这次怎么填 |
|---|---|
| 数据库类型 | Sqlite |
| 连接字符串 | Data Source=manual-demo.sqlite;journal mode=WAL |
| 自定义SQL模式 | 关闭 |
| 分表策略 | 不分表 |
| 数值历史表名 | 保持 historyNumberValue |
| 字符串历史表名 | 保持 historyStringValue |
| 变量信息表名 | 保持 variableInfo |
| 历史表脚本 | 留空 |
| 时区偏移 | 本例保持 +08:00 |
| 历史双库同步 | 不点击“新增”,本例只存一个数据库 |
- 点击“确定”,保持组和目标启用。目标应显示“在线”;组内应显示
1 变量,不要把目标上的“属性 0”误认为没有选变量。文件会建在网关运行目录,不是浏览器所在电脑;该目录必须允许网关写入。 - 等待几个采样周期,到“数据与告警 → 数据查询 → 历史数据”,选择
Demo_History_Target。 - 选择“数值数据”和刚才的变量。在时间面板中设置覆盖采集时间的范围,点击面板“确定”,再点筛选栏的刷新图标。应出现记录;再切换“图表”看曲线。固定值的曲线是水平线,不是没有采集。

完整查询、曲线和导出步骤见数据查询。
换用项目数据库
在目标属性中选择实际数据库类型,再填对应连接字符串。SQL Server、MySQL、PostgreSQL 等需要事先准备可访问的数据库服务与账号;SQLite 使用本机文件。
按项目确定独立数据库或表名,账号需要建表、查询、插入及清理历史数据的权限。先用一个点写入成功,再增加变量。多数据库、自定义 SQL 和双库镜像配置在下方按需查阅,不是首次存储的前置步骤。
目标基本信息
| 参数 | 默认值 | 如何配置 |
|---|---|---|
| 所属转发组 | - | 必须选择已保存的转发组;目标只接收该组范围内的变量。 |
| 目标名称 | - | 必填,同一转发组内不能重复。建议包含数据库和环境名称,便于数据查询时区分目标。 |
| 启用 | 开启 | 关闭时目标不会启动,也不会写入历史数据。 |
| 插件 | - | 选择“历史数据存储”。保存后不要改成其它数据转发插件。 |
| 日志级别 | Info | 常规运行使用 Info;排查连接或写入问题时临时提高到 Debug,验证完成后恢复。 |
| 启动超时 | 60 秒 | 目标初始化和数据库连接允许的最长时间,页面可填 1~3600 秒。 |
| 描述 | 空 | 可填写环境、数据库用途或负责人,便于运维识别。 |
数据库存储
| 参数 | 默认值 | 如何配置 |
|---|---|---|
| 数据库类型 | SqlServer | 选择 SqlServer、MySql、Sqlite、PostgreSql、QuestDb、TDengine 或 IoTDB。选择后必须使用对应连接串格式。 |
| 自定义SQL模式 | 关闭 | 关系型数据库、QuestDB、TDengine 和 IoTDB Table 可开启;IoTDB Tree 必须关闭。开启后使用“自定义模板配置”写入宽表。 |
| 分表策略 | 不分表 | SQL Server、MySQL、SQLite、PostgreSQL 可按天、周、月、季度、年或自定义服务分表;自定义策略需要已部署对应分表服务。QuestDB、TDengine 和 IoTDB 使用各自原生存储策略,分表字段不会改变其原生表布局。 |
| 连接字符串 | server=.;uid=sa;pwd=111111;database=test;Encrypt=True;TrustServerCertificate=True; | 这是开发环境示例,生产环境必须替换为实际地址、数据库、账号和密码。可复制的各数据库示例见连接字符串。 |
| 数值历史表名 | historyNumberValue | 标准模式下保存数值变量历史。关系型数据库、QuestDB、TDengine 和 IoTDB Table 会使用;自定义 SQL 模式改用模板中的 TableName。 |
| 字符串历史表名 | historyStringValue | 标准模式下保存字符串变量历史。名称只能使用目标数据库允许的标识符。 |
| 变量信息表名 | variableInfo | SQL Server、MySQL、SQLite、PostgreSQL 标准模式用于保存历史查询所需的变量信息;原生时序数据库通常不使用此表。 |
| 保留天数 | 3650 | 填写正整数。网关会将小于 1 的值归一为 1;清理任务按该值删除过期历史。QuestDB 使用 TTL,TDengine 使用 KEEP,IoTDB Table 使用删除任务。 |
| 历史表脚本 | 空 | 仅 SQL Server、MySQL、SQLite、PostgreSQL 的关系型脚本模式使用。通过表达式选择器选择已保存的动态 SQL 脚本,由脚本负责建表、写入和清理;QuestDB、TDengine、IoTDB 的原生写入路径不接受该脚本。开启“自定义SQL模式”时不要同时填写。 |
| 时区偏移 | +08:00 | 填写 +08:00、-05:00 这类固定偏移;留空表示 UTC。写入和查询必须使用同一个约定,否则会出现时间相差数小时或无时区后缀的问题。 |
| 自定义模板配置 | 空 | 仅“自定义SQL模式”使用。至少配置宽表名、时间列和需要写入的自定义列;三个 SQL 模板可以全部留空,由系统自动生成。 |
| 双库镜像 | 关闭 | 仅支持四种关系型数据库的标准历史表。需要主库和备用库同时写入时,打开后填写镜像配置;不能与自定义 SQL、历史表脚本或原生时序数据库组合。 |
连接字符串
下面的示例均使用独立数据库 thingsgateway_history。将 <密码>、地址和账号替换为实际值后,完整粘贴到“连接字符串”输入框。
| 数据库 | 可直接修改的连接字符串 | 说明 |
|---|---|---|
| SQL Server | Server=127.0.0.1,1433;Database=thingsgateway_history;User Id=history_user;Password=<密码>;TrustServerCertificate=True; | 默认端口 1433。账号需要建表、读写和清理权限。 |
| MySQL | Server=127.0.0.1;Port=3306;Database=thingsgateway_history;Uid=history_user;Pwd=<密码>;Allow User Variables=true;AllowLoadLocalInfile=true; | 默认端口 3306。数据库名写在 Database。 |
| SQLite | Data Source=DB/history.db;journal mode=WAL | 文件路径相对于网关运行目录;目录必须可写。journal mode=WAL 用于保持并发读写能力。 |
| PostgreSQL | Host=127.0.0.1;Port=5432;Database=thingsgateway_history;Username=history_user;Password=<密码>; | 默认端口 5432。自定义列使用 json 或 jsonb 时,列类型必须与 PostgreSQL 一致。 |
| QuestDB | host=127.0.0.1;port=9000 | 使用 REST SQL,默认端口 9000。QuestDB 没有需要填写的 Database 命名空间,使用不同表名隔离目标;启用 TLS 时可加 UseSSL=true。 |
| TDengine WebSocket | Host=127.0.0.1;Port=6041;Username=root;Password=<密码>;Protocol=WebSocket;db=thingsgateway_history | WebSocket 默认端口 6041。数据库键必须写小写 db=,不要写 Database=;协议必须写 Protocol=WebSocket。 |
| TDengine Native | Host=127.0.0.1;Port=6030;Username=root;Password=<密码>;Protocol=Native;db=thingsgateway_history | Native 默认端口 6030。运行环境还必须安装 TDengine Native 客户端库;无法安装时改用 WebSocket。 |
| IoTDB Tree | DataSource=127.0.0.1;Port=6667;Username=root;Password=<密码>;Model=tree;RootPath=root.thingsgateway;PoolSize=8;FetchSize=1024;ConnectionTimeoutInMs=5000;PoolWaitTimeoutInMs=10000;Compression=false;ZoneId=UTC;UseSSL=false | Tree 必须有 Model=tree 和 RootPath,不能填写 Database。模型由连接字符串决定。 |
| IoTDB Table | DataSource=127.0.0.1;Port=6667;Username=root;Password=<密码>;Model=table;Database=thingsgateway_history;PoolSize=8;FetchSize=1024;ConnectionTimeoutInMs=5000;PoolWaitTimeoutInMs=10000;Compression=false;ZoneId=UTC;UseSSL=false | Table 必须有 Model=table 和 Database,不能填写 RootPath。模型由连接字符串决定;只有 Table 可以使用自定义 SQL。 |
TDengine 连接串选项
TDengine 的连接串由 ; 分隔键值对组成。最少需要 Host、Port、Username、Password、Protocol 和 db。以下高级键可以按需追加:
| 键 | 示例 | 作用 |
|---|---|---|
timezone | timezone=Asia/Shanghai | 驱动连接时区。 |
connTimeout、readTimeout、writeTimeout | connTimeout=5000 | 连接、读取和写入超时,单位由 TDengine 驱动解释。 |
useSSL | useSSL=true | 启用 TLS;服务端和证书配置必须匹配。 |
enableCompression | enableCompression=true | 启用传输压缩。 |
autoReconnect | autoReconnect=true | 允许驱动自动重连。 |
reconnectRetryCount、reconnectIntervalMs | reconnectRetryCount=3;reconnectIntervalMs=2000 | 设置重连次数和间隔。 |
token 或 bearerToken | token=<令牌> | 使用令牌认证时填写,不能与密码配置混淆。 |
adapterHA | adapterHA=true | WebSocket 模式下启用 taosAdapter 实例发现和故障转移。 |
IoTDB 连接串选项
| 键 | 默认值 | 配置说明 |
|---|---|---|
DataSource、Port | 127.0.0.1、6667 | IoTDB 节点地址和 RPC 端口。集群可额外配置 NodeUrls,例如 NodeUrls=10.0.0.11:6667,10.0.0.12:6667。 |
Model | 无 | 必须是 tree 或 table,直接决定历史 writer 使用的物理模型。 |
RootPath | 无 | 仅 Tree 使用,例如 root.thingsgateway。 |
Database | 无 | 仅 Table 使用,例如 thingsgateway_history。Tree 和 Table 的 RootPath、Database 互斥。 |
PoolSize、FetchSize | 8、1024 | 连接池大小和查询抓取行数,必须为正整数。数据量较大时可提高 FetchSize,并观察内存。 |
Compression | false | 是否启用 RPC 压缩。 |
ZoneId | UTC | 服务端时区标识;要与数据查询的时区约定一致。 |
ConnectionTimeoutInMs、PoolWaitTimeoutInMs | 5000、10000 | 连接建立和连接池等待超时,单位毫秒,必须为正整数。 |
UseSSL、CertificatePath | false、空 | 开启 TLS 时必须同时提供证书路径。 |
标准表与数据库模式
| 选择 | 生成或使用的结构 | 注意事项 |
|---|---|---|
| SQL Server、MySQL、SQLite、PostgreSQL,关闭自定义 SQL | 数值表、字符串表和变量信息表 | 可使用分表、历史表脚本和双库镜像。 |
| SQL Server、MySQL、SQLite、PostgreSQL,开启自定义 SQL | 一张自定义宽表 | 使用“自定义模板配置”;不要再填写历史表脚本。 |
| QuestDB,关闭自定义 SQL | 数值表和字符串表 | 使用 QuestDB 原生 REST/CSV 写入和表分区;使用不同表名隔离目标。 |
| QuestDB,开启自定义 SQL | 自定义宽表 | 时间列必须是 QuestDB 支持的时间列,查询按模板中的时间列排序。 |
| TDengine,关闭自定义 SQL | 数值超级表、字符串超级表及变量子表 | SaveDays 映射为 KEEP;SplitTable 不改变 TDengine 表结构。 |
| TDengine,开启自定义 SQL | 自定义超级表/子表宽表 | 自定义建表 SQL 必须符合 TDengine 超级表语法;留空时由系统生成。 |
| IoTDB Tree | 设备路径下的 measurement | 不使用自定义宽表和自定义 SQL;历史数据按设备路径查询。 |
| IoTDB Table,关闭自定义 SQL | 标准表模型数值表和字符串表 | 使用 TIME、TAG、ATTRIBUTE、FIELD;不使用 Tree 的 RootPath。 |
| IoTDB Table,开启自定义 SQL | Table 模型自定义宽表 | 只能在 Table 模型开启;历史查询页面按实际返回列展示。 |
IoTDB 的物理模型直接由连接串中的 Model 决定;RootPath 和 Database 必须填写与模型匹配的选项,Tree 和 Table 不能混用。QuestDB、TDengine 和 IoTDB 使用各自的原生连接和写入方式。历史表脚本只适用于四种关系型数据库,且与自定义 SQL 模式互斥。
QuestDB 自动表使用原生分区和 TTL,TDengine 使用 KEEP;自定义建表或清理 SQL 填写后,以模板语句为准。IoTDB 的清理由目标清理任务执行。
自定义模板配置
开启“自定义SQL模式”后,在“自定义模板配置”中定义一行宽表如何容纳多个变量。三个 SQL 模板都可以留空;留空时系统按表结构、分组方式和列映射自动生成。
宽表字段
| 字段 | 默认值 | 如何配置 |
|---|---|---|
TableName | CustomHistoryData | 数据库中的实际宽表名。只能使用目标数据库允许的标识符,不能写成带密码或连接串的文本。 |
TimeColumnName | CollectTime | 每一行的时间列名。系统按该列查询、排序和清理;QuestDB、TDengine、IoTDB Table 还会把它映射到原生时间列。 |
TimeColumnType | datetime | 填目标数据库支持的时间类型,例如 SQL Server datetime2、PostgreSQL timestamp、QuestDB TIMESTAMP。 |
Columns | 空列表 | 添加业务列。每列配置见下表;未添加列时宽表只有时间列,通常没有可写的业务值。 |
GroupMode | CollectGroup | 选择按采集组、设备名称、备注1至备注5或“不分组”。同一分组和时间窗口内的变量会合并为一行。 |
GroupTimeWindowMs | 1000 | 时间窗口,单位毫秒,必须大于 0。窗口越大,合并行越多;不分组时每个变量独立成行。 |
CustomInitSQL | 空 | 自定义建表 SQL。留空自动生成;填写后只替换初始化动作。 |
CustomInsertSQL | 空 | 自定义插入 SQL。留空自动生成;填写后只替换写入动作。 |
CustomDeleteSQL | 空 | 自定义清理 SQL。留空使用目标默认清理;填写后使用 SaveDays 对应的 {Days}。 |
Columns 子项
| 字段 | 如何配置 |
|---|---|
ColumnName | 数据库列名,不能重复,必须符合目标数据库标识符规则。 |
ColumnType | 数据库列类型,默认 nvarchar(200);也可使用 float、double、decimal(18,3)、int、bit、varchar(200)、json 或 jsonb。类型必须是当前数据库支持的类型。 |
VariableName | 优先匹配转发变量名称;也可填写 DeviceName、CollectGroup、Remark1 至 Remark5 读取设备或变量属性。留空时按 ColumnName 匹配同名变量。 |
DefaultValue | 找不到映射值时使用的默认值;留空则写入 NULL。默认值文本必须能转换为 ColumnType。 |
IsRequired | 开启后自动建表生成 NOT NULL。若某次转发没有值且没有可用默认值,数据库会拒绝该行。 |
最小示例:添加 temperature 列,类型填 float,映射变量名填 Temperature;选择“按设备名称分组”,窗口填 1000。同一设备 1 秒窗口内的 Temperature 会写入同一行。
SQL 占位符
占位符只用于 SQL 结构,表名和列名会按目标数据库自动引用,数据值由参数或原生编码处理。不要把密码、Token 或完整连接串写入模板。
| 占位符 | 可用于 | 替换内容 |
|---|---|---|
{TableName} | 建表、插入、删除 | 带数据库引用符的宽表名。 |
{TimeColumn} | 建表、插入、删除 | 带数据库引用符的时间列名。 |
{Columns} | 建表 | 所有自定义列的带引用符列名列表。 |
{ColumnNames} | 插入 | 插入列清单,不包含时间列。 |
{TimeParam} | 插入 | 时间参数名,通常为 @CollectTime。 |
{ValueParams} | 插入 | 自定义列对应的参数列表。 |
{UpdateSet} | 插入 | 自定义列更新表达式,可用于支持 UPSERT 的方言。 |
{Days} | 删除 | 当前目标的保留天数。 |
填写自定义 SQL 后,必须在目标日志和数据库客户端中各执行一次验证。不同数据库的引号、时间类型、UPSERT 和超级表语法不能互相套用。
双库镜像设置
| 参数 | 默认值 | 如何配置 |
|---|---|---|
| 同步到备用数据库 | 关闭 | 打开后主库和备用库都会写入标准历史表。 |
| 备用数据库类型 | SQL Server | 选择 SQL Server、MySQL、SQLite 或 PostgreSQL;不能选择 QuestDB、TDengine 或 IoTDB。 |
| 备用数据库连接 | 空 | 填备用库完整连接字符串;主库和备用库必须是不同的物理数据库。 |
| 本机待同步上限 | 262144 | 本机镜像 Journal 的最大待提交记录数。 |
| 对端待同步上限 | 1048576 | 对端可见但尚未复制的最大记录数,不能小于本机待同步上限。 |
| 自动检查间隔(分钟) | 0 | 0 表示不启动定时一致性检查,最大 1440。 |
| 自动补齐差异 | 关闭 | 一致性检查发现缺失记录时,是否自动双向补齐。启用前先确认两端权限和容量。 |
变量映射
该目标没有目标变量级附加属性。变量是否进入目标以及触发方式由转发组范围和组变量设置决定;保存目标后无需在“变量属性”侧栏重复配置。
转发组策略
历史目标不再配置目标级“默认采样策略”“采样间隔”或“条件表达式”。这些选项应在转发组编辑页设置;手动变量列表可以对单个组成员覆盖更新模式。
历史数据存储插件没有目标变量级的附加采样属性,不需要在“变量属性”中重复配置采样。
| 转发组参数 | 默认值 | 如何配置 |
|---|---|---|
| 变量范围 | 手动选择 | 全部变量、手动选择、采集设备 或 采集组。范围决定哪些变量有资格进入历史目标。 |
| 范围配置 | 空 | 选择采集设备或采集组时填写逗号分隔名称;全部和手动模式不使用此字段。 |
| 触发模式 | 变化 | 变化 在变量变化时触发,定时 仅由调度触发,定时或变化 两者都触发。 |
| 定时间隔 | 1000 | 可填毫秒数字(如 1000)或时间文本(如 00:00:01)。定时或变化模式下必须填有效间隔。 |
| 在线过滤 | 关闭 | 开启后组级先过滤离线变量;需要记录离线采集值时关闭。 |
| 分批模式 | 不分批 | 可选全部、按数据组、按设备或按采集组拆分一次触发的数据。 |
| 最大批量 | 1000 | 单次批处理允许的最大变量数量,范围 1~100000。 |
使用手动范围时,在组变量列表中还可以设置“启用”“数据组”“更新”“组触发”和“别名”。这些字段只影响该变量是否进入组、如何触发和如何命名,不会替代目标数据库连接配置。
缓存与容量
这些属性在“目标属性”页的“缓存与可靠性”或“容量限制”分组中设置。
| 参数 | 默认值 | 如何配置 |
|---|---|---|
| 启用失败重试缓存 | 开启 | 建议保持开启,数据库恢复后自动补发失败记录。关闭后,写入失败的数据不再保留重试。 |
| 缓存文件最大行数 | 262144 | CacheDB 出站队列的最大行数。超过上限会删除最旧数据;按断网时长和写入速率预留磁盘空间。 |
| 上传分片大小 | 2000 | 每次写入或补发的最大记录数。数据库吞吐较低时减小,批量接口吞吐较高时可适当增大。 |
| 内存队列上限 | 100000 | 内存缓冲的最大记录数。超过后会尽早转入 CacheDB,仍超限时可能丢弃旧数据;不应按磁盘容量替代内存评估。 |
| 过滤离线数据 | 关闭 | 开启后,目标出队时过滤离线变量值。转发组也有同名在线过滤,只有两处都允许时数据才会进入目标。 |
| 并发上传数量 | 1 | 插件发送实现按需使用。历史目标默认串行上传;除非已确认数据库和网络可以承受,否则保持 1。 |
目标调试
进入“开发配置 → 数据转发”,选择转发组和历史目标,打开“调试”。
| 功能 | 作用 |
|---|---|
| 转发管线 | 查看变量范围、触发模式、过滤结果和进入历史目标的数据摘要。 |
| 缓存出站 | 查看内存队列、CacheDB 待处理数量、失败重试保留状态和最近错误。 |
转发管线

“转发管线”用于确认变量范围、触发方式和进入目标的数据摘要。
缓存出站

“缓存出站”显示内存队列、CacheDB 待处理数量、保留重试数和最近错误。启用离线缓存后,数据库不可用期间的待写记录会在恢复后继续写入。
保存与验证
- 保存转发组和目标后,确认目标状态为“在线”,目标日志没有初始化错误。
- 让一个已纳入范围的变量产生一次变化,或等待一个定时周期。
- 在目标“调试”页查看“转发管线”和“缓存出站”。成功写入时,待处理数量应下降,最近错误应为空。
- 在数据库客户端查询最新记录,逐项核对变量标识、设备名、值、空值、采集时间和记录时间。自定义 SQL 模式还要核对模板中的每个列名。
- 在“数据查询 → 历史数据”中切换当前目标,确认总数、分页、最新排序和浏览器时区显示与数据库结果一致。
时间与查询
时区偏移是写入端的存储约定,不是浏览器时区设置。浏览器使用Asia/Shanghai时,若目标按 UTC 存储,接口可能返回带Z的 UTC 时间;若目标按+08:00存储,通常显示为本地墙钟值。两种结果表示的是同一时刻,关键是配置和查询使用同一偏移。- 标准模式使用固定的数值表和字符串表;自定义 SQL 模式按宽表实际列返回。不要用
variableId、value等标准字段名去猜自定义宽表列名。 - QuestDB、TDengine 和 IoTDB 原生模式可能使用原生时间列或整数时间戳。查询页应以返回列名和时间列类型为准,并检查最新行的所有字段。
常见问题
| 现象 | 检查顺序 |
|---|---|
| 目标无法启动 | 检查数据库服务、连接字符串键名和端口、账号权限、表名标识符、IoTDB 模型,以及是否错误地同时启用了互斥选项。 |
| TDengine 提示找不到数据库 | 确认连接串使用 db=数据库名,不是 Database=数据库名;WebSocket 使用 6041,Native 使用 6030,并确认 Protocol 与端口一致。 |
| IoTDB 连接失败 | Tree 必须配置 Model=tree;RootPath=...,Table 必须配置 Model=table;Database=...;不要同时填写 RootPath 和 Database。启用 TLS 时检查 CertificatePath。 |
| 没有新增历史记录 | 检查转发组变量范围、组是否启用、触发模式、定时间隔、组在线过滤和目标在线状态。历史目标没有单独的目标级采样配置。 |
| 数据库恢复后没有补写 | 开启“启用失败重试缓存”,查看“缓存出站”的 CacheDB 待处理数量和目标日志。关闭缓存时,退避期间失败数据可能已被丢弃。 |
| 查询数量或最新时间不对 | 先确认查询的目标名称和表名,再确认排序字段、时间范围、分表策略和时区偏移。自定义 SQL 模式按实际宽表列查询。 |
| 自定义 SQL 建表或写入失败 | 检查 Columns 的列名、类型、映射变量和必填值;确认占位符、引号、时间类型和 UPSERT 语法符合当前数据库方言。 |
| 双库镜像保存失败 | 检查备用连接、物理库是否与主库不同、两端是否都是关系型数据库,并确认未开启自定义 SQL 或历史表脚本。 |
