
我挑了个周末把 TDengine 从部署到常用功能做了一轮完整测试。不是跑个 demo 就收工那种而是按照生产需求把建库、建表、写入、查询、连续查询、可视化连接、时序预测函数全部过了一遍。这轮 TDengine 测试过程中遇到不少问题其中最典型的是三个高频报错——license 外部查询限制0x83a、DBeaver 连接时 JDBC jar 无从下手、Holt-Winters 预测函数报 double 类型错误几乎占了我所有排障时间的一半。文章把这些测试过程、命令、报错和排查思路完整写出来适合正在做时序数据库选型评估、或者刚上手 TDengine 准备搭建测试环境的团队参考。既然踩坑过程已经完整走了一遍我尽量把当时怎么想的、后来怎么改的也写清楚让这篇东西不只是日志而是一份能照着操作的测试手记。1. 这次测试要验证什么我的TDengine评估清单1.1 为什么在时序库选型时盯上了TDengine做物联网平台的数据存储选型时我面前摆着 InfluxDB、Prometheus、TDengine 等几个候选。团队最在意的其实是三件事写入吞吐能不能扛住千万点/天的数据量查询要不要写复杂的分布式系统运维成本是不是一个后端兼职就能搞定。TDengine 的“一个进程就是一套库”的设计显然有吸引力而且它自带 SQL 接口团队不用学习新的查询语言。但我不会只因为官网的 benchmark 就采信所以决定实际拉环境做一轮完整的测试用真实数据验证。这里多说一句不同时序库的“快”不是一个维度的快。InfluxDB 的写入模型围绕 tag 和 fieldPrometheus 适合 pull 模型和监控告警TDengine 则是典型的表模型加标签设计它把设备建模成超级表每个设备一张子表查询时自动按标签路由这种模式更适合我做的那种“设备多、指标多、按设备查询频繁”的物联网场景。选择它测试本质上是在验证这个模型是否真的像文档说的那样省心。另外还有一个现实原因团队里多数人只会 SQL。如果选型结果是一个需要学习 Flux 或 PromQL 的库培训成本也是一笔不小的开销。TDengine 的 SQL 兼容性让我们可以复用大量已有的 SQL 经验这条对于中小团队尤其重要所以我把它列进测试重点而不是只盯着压测数字。1.2 测试范围和验收口径测试之前先把范围定清楚免得测到一半变成漫无目的的瞎点。基础功能建库、建超级表、建子表、标签写入、普通查询、聚合查询。性能感知写入吞吐量、聚合查询响应时间在单机环境下的表现。稳定性连续写入 24 小时数据、连续查询、重启后的数据完整性。工具链DBeaver 可视化连接、JDBC 驱动是否能正常工作。高级能力Holt-Winters 等时序预测函数是否符合需求。验收口径定为单机 8C16G 环境下写入吞吐不低于 10 万点/秒查询 P95 不超过 1 秒DBeaver 和 JDBC 能稳定连接预测函数能输出合理结果。后来的所有测试都围绕这个清单展开。有人可能觉得第一轮测试不用定这么细但时序数据库的测试和业务功能测试不一样没有一个明确的“过/不过”标准很容易被某一次的漂亮压测数据带偏等真正上线才发现查询场景没覆盖到返工成本非常高。我还额外给自己加了一条规则测试过程中所有参数修改都要记下原因。这轮测试结束后回看很多当时觉得“无所谓”的参数比如 FQDN 配置、时区设置、缓存模式最后都成了某个报错的根因。如果当时没有记录排障时连自己改过什么都不清楚那就真要抓瞎了。2. 部署这台测试环境我踩过的配置坑2.1 版本与部署方式的选择部署方式有 RPM、tar 包、Docker。我最终用了 Docker原因是测试机不想污染系统环境而且 Docker 版本的配置映射和日志管理更清晰。版本上没有选最新的 3.3.x当时还是 beta选了 3.2.x 稳定版。这点值得强调时序数据库是基础数据设施不能拿 beta 版做基线测试否则后面排查问题会把版本 bug 和自身误操作混在一起最后变成双重排障痛苦翻倍。实际启动命令我用了这一条docker run -d --name tdengine-test \ -p 6030:6030 -p 6041:6041 -p 6043:6043 -p 6044:6044 -p 6045:6045 -p 6046:6046 \ -v /data/tdengine:/var/lib/taos \ -v /var/log/tdengine:/var/log/taos \ tdengine/tdengine:3.2.3.0端口这块值得展开说。6030 是原生连接端口客户端 taos、taosdump、JDBC native 方式都用它6041 是 taosAdapter 的 REST 端口DBeaver 的 REST 连接和很多第三方工具都走这里6043 到 6046 是 taosAdapter 的其他服务端口比如 statsd、opentsdb 兼容接口。很多新手第一次只映射 6030后面用 6041 才发现根本连不上回头补映射就得重启容器如果不小心忘了数据目录映射还可能把容器里的数据弄丢。2.2 Docker 部署的关键参数启动只是第一步真正决定测试体验的是目录和时区。我把数据目录映射到了宿主机 /data/tdengine日志映射到 /var/log/tdengine这样想看数据文件占用和日志都不用进容器直接在宿主机上就能操作。时区问题上TDengine 对时间戳处理非常严格默认使用 UTC如果应用层不统一时区写入和查询会差 8 小时。三年前我第一次用 TDengine 时就被坑过一次凌晨排查数据为啥对不上最后发现是时区问题气得够呛。这次我在容器启动时直接把宿主机的时区映射进去docker run -d --name tdengine-test \ -e TZAsia/Shanghai \ ...同时也在 taos.cfg 里加了timezone GMT8两边对齐才敢开始写测试数据。如果你的客户端是用 JDBC 连的连接串里也要带时区参数否则 JDBC 驱动默认的时区会和服务端不一致。这条我记得在 DBeaver 连接测试里也踩过后面会提到。2.3 部署完必须先核对的三项配置第一FQDN。taosd 默认使用机器的 hostname 作为 FQDN如果 hostname 解析不对客户端用 IP 连可以用域名连就报unable to resolve FQDN。测试环境里我直接在 /etc/hosts 里加了一行本机映射一劳永逸地解决。很多人不理解为什么 TDengine 非要 FQDN其实它内部节点间通信、客户端连接串解析都依赖一个统一的名称不统一就会出现“本地能连、远程连不上”的诡异现象。第二时区。上面提到过这里补充一个验证方法执行SELECT NOW();看返回时间是不是当前时间。差 8 小时就说明时区还是 UTC赶紧改配置重启。第三初始密码。TDengine 默认 root 密码是 taosdata生产环境必须改。测试环境虽然可以偷懒但我还是建议在测试阶段就改成强密码因为后面 DBeaver、JDBC 压测工具都要用同一个密码提前统一避免各配各的。部署完以后我习惯跑一条冒烟 SQL 确认服务正常SHOW DATABASES;看到默认的information_schema、performance_schema以及我新建的testdb出现才算部署通过。3. 功能基线测试建表、写入、查询一条线3.1 建库建表参数的实测影响Docker 环境就绪后第一件事不是着急写数据而是把建库参数过一遍。我重点验证了三个参数vgroups、cacheModel、precision。vgroups决定了数据库划分成多少个虚拟节点组。在单机测试环境下从 1 改到 8 的过程中可以明显感受到采集类数据的写入并发能力增强但 vgroups 开太高反而浪费内存因为每个 vgroup 会占用独立的 vnode 资源。对测试机 8C16G我选了 4。如果是集群部署这个值还要结合节点数和 CPU 核数来分配不能拍脑袋。cacheModel我用的both表示行缓存和列缓存都开。这个参数对查询响应影响很大我实际用 1000 万条数据对比过both 比 none 在点查场景快约 3 倍。代价是内存占用会上升如果你的测试数据量特别大要留意宿主机内存够不够。PRECISION我选了毫秒。如果业务只需要毫秒精度用 ms 可以显著减少时间戳存储开销。微秒和纳秒的数据文件体积、导入性能都会不同这个在选型阶段就要想清楚因为表一旦建好精度是不能动态修改的。建库语句如下CREATE DATABASE testdb BUFFER 256 CACHEMODEL both COMP 2 DURATION 30d PAGES 256 PAGESIZE 16K PRECISION ms KEEP 365d;然后建超级表和子表。超级表的设计很关键——标签是索引和分组的依据量测字段是存储计算的依据。我建的测试表长这样CREATE STABLE testdb.device_t (ts TIMESTAMP, val FLOAT, status INT) TAGS (device_id NCHAR(64), site_id INT);子表按设备维度创建一个设备一张子表CREATE TABLE testdb.device_001 USING testdb.device_t TAGS (DEV-001, 1);这里验证了一个很实际的点如果不提前建子表TDengine 在写入时可以自动建表但自动建表模式下标签的维护不如手动清晰。测试阶段还好生产环境建议程序里显式建表把标签来源管住否则后面按标签过滤统计时会发现一堆不规范标签处理起来很麻烦。3.2 写入测试怎么做才不算应付写入测试我用 taosBenchmark 先压了一轮又写 Python 脚本模拟真实采集端写入。taosBenchmark 是官方压测工具通过配置文件指定设备数、行数、线程数、数据模式。我压测时用的配置摘要如下{ name: td_write_test, host: 127.0.0.1, port: 6030, user: root, password: taosdata, dbname: testdb, create_child_table: true, insert_rows: 10000000, insert_interval: 0, num_of_threads: 10, childtable_count: 100, childtable_prefix: device_, data_source: rand }10 个线程、100 张子表、1000 万条随机数据实测写入完成时间大约 28 秒算下来单机写入大概 35 万点/秒远超 10 万点/秒的验收线。不过我提醒一句taosBenchmark 的随机数据没有业务含义它检验的是数据库的写入通道能力不是端到端能力。端到端要看自己采集程序的真实打包方式和网络延迟所以之后我又用 Python 按 1000 条一批的 batch 从客户端写入模拟真实采集结果接近 18 万点/秒也过了验收线。写完之后一定要验证数据有没有真的落库。用SELECT COUNT(*)和SELECT LAST_ROW(*)抽查避免压测工具显示成功但实际数据错乱。我在测试中就用一条简单的统计验证过SELECT COUNT(*) FROM testdb.device_t;返回 10000000和预期一致才认为写入链路是通的。3.3 查询测试重点看什么写入没问题之后查询测试我重点做了三类。第一类是点查查某个设备某个时间点的值这决定了很多看板实时刷新的体验。用主键 ts 过滤响应几十毫秒。第二类是时间范围聚合比如按分钟统计某站点的平均温度和最大温度用 INTERVAL 子句。1000 万条数据范围查询加聚合响应也在百毫秒级。第三类是标签过滤查询类似WHERE site_id 1 AND ts ...验证标签条件能否走索引。一个容易忽略的点是TDengine 的查询性能和PARTITION BY的使用强相关。如果经常按设备分组聚合最好显式用PARTITION BY tbname或者子表名字段否则数据库得全表扫描聚合量一大就慢。我在测试里对比了有 PARTITION BY 和没 PARTITION BY 的同一条聚合语句数据量 1000 万时响应时间差了一个数量级这一点非常直观。3.4 连续查询与预聚合验证连续查询Continuous Query是时序场景用得比较多的功能。它本质上是一个自动按窗口执行的预聚合任务可以把原始明细聚合成统计指标存到另一张表里。我建了一个 CQ把设备温度按 1 分钟窗口聚合CREATE CONTINUOUS QUERY testdb.cq_1min ON testdb BEGIN SELECT _wstart, AVG(val) AS avg_tmp, MAX(val) AS max_tmp INTO testdb.device_t_1min FROM testdb.device_t PARTITION BY tbname INTERVAL(1m); END;实测发现 CQ 默认从当前时间开始执行历史数据不会回填。如果业务需要回填历史聚合结果得自己写INSERT INTO SELECT语句而不是指望 CQ。另外 CQ 的执行频率跟时间窗口大小有关窗口越小时延越低但对系统资源占用也越高测试机运行一段时间后明显看到 CPU 占用上升。这个在规划生产时要注意CQ 不是免费的预计算它是用持续的 CPU 开销换查询时的低延迟。4. 高频报错之一license 外部查询限制0x83a4.1 复现路径测试过程中我碰到了第一个让人头大的报错TDengine error (0x83a): query denied by license: external query is restricted这个报错第一次出现是我想在另一台机器上用 DBeaver 通过 REST 方式连接测试环境的 TDengine 查询表数据。本地用 taos 命令行查没问题但从外部客户端一查就报这个错。网上搜了一下这个报错在 TDengine 新版本里并不少见很多人第一反应是 license 过期了但查了 license 状态又是正常的。我当时也反复确认了授权时间发现根本没有过期这就让我开始怀疑问题不在授权本身而在“外部查询”这个判定逻辑上。4.2 错误机制拆解TDengine 的 license 机制分免费版和企业版授权。免费版默认安装后会带一个本地开发授权这个授权通常只允许本地或者内网有限范围内的访问。当服务端收到的查询请求来自它认为的“外部客户端”时就会触发external query is restricted的限制。什么是“外部”在 TDengine 的判定逻辑里关键不是 IP 是不是内网而是客户端请求的到达方式和服务端的 FQDN 配置是否匹配。具体来说如果客户端通过 taosAdapter 的 REST 接口6041 端口发起查询而 taosAdapter 把请求转发给 taosd 时带上的请求来源被判定为外部license 就会认为这是外部 query然后拒绝。同样原生连接6030 端口如果客户端没有正确配置 FQDN让 taosd 认为请求来自外部也会触发这个错误码。提示0x83a 是十六进制错误码客户端工具通常直接显示这一串别费劲去数它对应十进制多少重点看后面那句“external query is restricted”它已经点明了问题域。4.3 排查和规避的闭环我最终的解决方案是把连接方式统一收口到“内网原生连接 本机验证 REST”统一 host 名解析在客户端机器和服务器上把 hosts 文件里 FQDN 的映射改成同一个地址让两边看到的服务器名称一致。确认 license 范围执行SHOW LICENSE;检查到期时间和允许的节点数都在正常范围。调整应用层连接方式DBeaver 改用 native JDBC6030 端口连接不再走 REST外部 API 场景则临时使用 taosAdapter 所在本机执行的代理方式。恢复后同一条查询语句在 DBeaver 中能正常返回。如果你买的是企业版授权这个问题基本不存在0x83a 主要用于约束免费版的外部访问。所以如果只是个人测试建议所有查询都从服务器本机或同 FQDN 区域发起能省掉很多排查时间。生产环境如果准备用 REST 接口对外提供数据服务就必须确认 license 是否包含外部查询能力这一点建议在选型阶段就跟官方确认清楚别等上线了再处理。这条链路走完我对 TDengine 的连接机制理解深了不少它不是一个“你连上了就能查”的数据库节点身份、授权范围、访问路径三者必须一致。很多第三方工具的报错根因都不在工具本身而在服务端的访问策略配置。5. 高频报错之二DBeaver 连不上 TDenginejar 到底怎么配5.1 可视化工具选型与 jar 下载DBeaver 连 TDengine 的坑核心就是 jar 包。DBeaver 默认驱动库搜不到 TDengine 的驱动得手动添加。这就引出热词里“tdengine中jar下载dbeaver”的问题很多人不知道 jar 从哪下、下哪个。我自己试下来可行的路径有两条从 Maven 中央仓库搜taos-jdbcdriver找到对应版本的 jar。从 TDengine 官网下载页的 JDBC 驱动部分下载。版本对齐很重要。如果你的 TDengine 服务端是 3.2.x就别去下载 2.x 的 taos-jdbcdriver连接协议变化会导致握手失败或报无法识别。我用的是 3.2.7 版本的驱动 jar。下载后在 DBeaver 里操作路径是数据库 - 驱动管理器 - 新建填入驱动名称然后“添加文件”把 jar 加进去。类名 natives 方式填com.taosdata.jdbc.TSDBDriver如果走 REST 方式则用com.taosdata.jdbc.ws.TSDBDriver5.2 JDBC URL 和连接配置新建连接时 URL 很关键。native 方式我填jdbc:TAOS://192.168.1.100:6030/testdb用户名 root密码 taosdata。如果 DBeaver 所在机器没安装 TDengine 客户端native 方式可能失败因为 TAOS 驱动需要 libtaos.so 或 taos.dll。这时有两个选择一是安装 TDengine 客户端官方有独立的 client 包二是改用 REST/websocket 方式URL 写成jdbc:TAOS-RS://192.168.1.100:6041/testdbREST 方式不需要本地安装客户端但需要确保 6041 端口taosAdapter能通。我在 DBeaver 里两个方式都试过native 方式性能更好REST 方式胜在省事。实际项目里如果你的分析人员电脑上不想装任何客户端REST 方式会更友好但要注意前面提到的 license 外部查询限制可能也会在这里冒头。5.3 连接不上的典型排查顺序连接测试时除了驱动类名和 URL还有三个问题经常碰到我按照排查顺序列出来端口不通DBeaver 测试连接超时大多是因为容器只映射了 6030没映射 6041。先用telnet ip port验证端口。驱动类找不到通常是 jar 没加载进去或者驱动类名写错。DBeaver 驱动管理里设置完要确认 jar 出现在库列表里。用户名密码带特殊字符如果密码里带特殊字符URL 里记得做 URL 编码否则驱动解析会出错。我自己测试时的最小配置如下配置项native 方式REST 方式驱动类com.taosdata.jdbc.TSDBDrivercom.taosdata.jdbc.ws.TSDBDriverJDBC URLjdbc:TAOS://IP:6030/dbnamejdbc:TAOS-RS://IP:6041/dbname必要条件需安装 taos client 或本机可解析需 taosAdapter 运行正常这张表在我后来的团队内部培训里也发过基本照着填就不会错。6. 高级函数测试Holt-Winters 的 double 类型报错问题6.1 报错现场与最小复现测试到预测函数环节时我打算用 Holt-Winters 对温度序列做趋势预测第一次执行就遇到类似下面这样的报错Invalid data type: holtwinters requires DOUBLE input或者在某些版本里表现为failed to convert to double。热词“tdengine holtwinters 怎么double报错”说的就是这个问题。最小复现其实很简单我在普通表上直接跑SELECT holtwinters(val, 24, 12) FROM testdb.device_001;如果 val 在建表时定义成 FLOAT 或 INT部分版本会直接抛类型错误。当时我建表时把温度字段定义为 FLOAT觉得足够用了结果在 Holt-Winters 这一步就卡住了。6.2 函数对类型的隐性要求Holt-Winters 是一个指数平滑类预测算法核心是把历史序列拆成水平项、趋势项、季节项再迭代更新。由于算法在迭代过程中会有大量的乘法和累加为了避免累积误差和溢出TDengine 的实现要求输入序列必须是 DOUBLE窗口参数是整型输出也是 DOUBLE。问题在于很多业务表在建模时不会刻意把所有量测都建成 DOUBLEFLOAT 和 INT 建模很常见。函数文档里写了“该函数输入列的类型为 DOUBLE”但实际使用时很容易忽略。还有一种情况是嵌套查询的中间结果被推断成了非 DOUBLE 类型比如从子查询中取 AVG 之后的列如果不做转换照样报错。6.3 修复方案与验证结果解决办法很直接显式用 CAST 把输入列转成 DOUBLE。例如SELECT holtwinters(CAST(val AS DOUBLE), 24, 12) FROM testdb.device_001 WHERE ts 2024-01-01 00:00:00 AND ts 2024-02-01 00:00:00;如果原表字段本来就是 DOUBLE则不需要 CAST。验证时我还发现Holt-Winters 的输出列名默认是函数名如果后面要继续嵌套查询或写入结果表最好手动起一个别名SELECT holtwinters(CAST(val AS DOUBLE), 24, 12) AS forecast_val FROM testdb.device_001 ...;另外窗口参数的设置直接影响预测效果和报错概率。比如数据是 1 分钟采集一次一个季节周期可能是 24 小时那周期参数建议设成 1440。窗口太小预测值没有意义窗口太大计算量会上升还可能不收敛尤其在数据本身噪声很大的情况下。实测我的温度数据用 24 作为周期得到的结果和真实趋势基本吻合误差在工程可接受范围内。预测函数目前不建议直接对整张大表跑最好先 PARTITION BY 子表或先 GROUP BY 时间窗口把数据裁剪到位再调用 Holt-Winters。否则一个周期参数乘以全表数据量计算时间会非常难看。我最初直接对 1000 万行跑了一次等了快两分钟还没出结果后来改成按子表逐台调用单次毫秒级返回效果立刻就不一样了。7. 测试收尾几条花时间换来的经验这轮 TDengine 测试跑下来我最大的感受是这个数据库确实能打但前提是别把它的默认配置当生产配置。上面几个报错一个来自 license 对访问路径的限制一个来自 JDBC 驱动版本与连接方式的选择一个来自数据类型的隐式要求每一个都不是 bug而是“没读懂约定”。如果让我给后来者总结就三条部署先统一 FQDN 和时区第三方工具能走 native 就先走 native非要走 REST 前先确认 license调预测函数前先把列转成 DOUBLE窗口参数按真实业务周期来。还有一个小技巧是TDengine 的日志目录值得经常翻。很多连接报错在 taosd 日志里会有更完整的上下文比如请求来源 IP、使用的连接协议、license 判定结果这些信息比客户端工具提示更详细。测试阶段养成看日志的习惯能少走很多弯路。测试还在继续后面我准备验证多节点集群下的 vnode 分布和扩容表现重点观察数据重分布时对在线查询的影响。如果有结论再单独写一篇。上面这些坑如果你们也踩到了照着排查应该能省不少时间。