本文将通过一个完整的实例,演示如何在 IoTDB 2.0.x (以2.0.6版本为例) 中对表模型进行数据写入,并通过自定义工具分析底层TsFile文件结构,帮助开发者深入理解IoTDB的数据存储机制。
一、生成测试SQL文件
为了批量写入数据,我们首先编写一个Python脚本,用于生成包含建表、插入数据的SQL文件。这种方法在需要进行数据库操作的性能测试或功能验证时非常高效。
import random
# 创建数据库和表
sql1 = "create database demo_test;\n"
sql2 = "use demo_test;\n"
# 注意:时间戳列可以省略
sql3 = "CREATE TABLE t1(id INT FIELD, gauss_val FLOAT FIELD, uniform_val INT FIELD, district STRING FIELD);\n"
with open('test_sql.txt', 'w', encoding='utf-8') as f:
f.write(sql1)
f.write(sql2)
f.write(sql3)
# 插入数据
test_point_num = 32000
for i in range(test_point_num):
gauss_val = random.gauss(0, 1) * 100 + 300
uniform_val = (int) (random.random() * 100)
# 注意时间戳
sql = f"INSERT INTO t1 VALUES ({i+1}, {i + 1}, {gauss_val:.3f}, {uniform_val}, 'district_{i%10}');\n"
f.write(sql)
# 刷盘
f.write("FLUSH;\n")
执行上述Python脚本后,生成的test_sql.txt文件前几行内容如下:
create database demo_test;
use demo_test;
CREATE TABLE t1(id INT32 FIELD, gauss_val FLOAT FIELD, uniform_val INT32 FIELD, district STRING FIELD);
INSERT INTO t1 VALUES (1, 1, 424.288, 61, 'district_0');
INSERT INTO t1 VALUES (2, 2, 227.418, 5, 'district_1');
二、控制TsFile中Page的点数
IoTDB的TsFile文件是数据存储的核心。默认情况下,一个Page(数据页)包含多达1万个数据点(前提是页面大小未超过64KB)。过多的点数可能会影响基于统计数据的过滤性能。我们可以通过SQL的SET命令动态调整每个Page存储的最大点数。
将以下语句添加到test_sql.txt文件的第一行:
set configuration "max_number_of_points_in_page" = '1000';
需要注意的是,如果通过源码方式(运行ConfigNode.java和DataNode.java)启动IoTDB服务,可能会因为配置文件路径问题导致SET命令执行报错(错误信息通常提示找不到iotdb-system.properties文件)。

不过,即使报错也无需担心。因为max_number_of_points_in_page是热加载参数,IoTDB服务进程已经将其值临时修改为1000,只是未能写入配置文件持久化。后续我们将通过分析工具验证这一修改是否生效。
三、编写自定义客户端MyCLi
直接使用IoTDB CLI逐条执行SQL速度较慢。为了提高效率,我们可以基于其代码编写一个自定义客户端MyCli.java,用于自动读取并执行整个SQL文件。
package org.apache.iotdb.cli;
// ... 导入包(此处省略,保留关键代码结构)
/**
* MyCli - 自动执行SQL文件的客户端
* args[]: -h 127.0.0.1 -p 6667 -u root -pw root -f /path/to/sql/file.txt
*/
public class MyCli extends AbstractCli {
private static CommandLine commandLine;
private static final Properties info = new Properties();
// 新增文件参数
private static final String FILE_ARGS = "f";
private static final String FILE_NAME = "file";
private static final String FILE_ARGS_LONG = "file";
/**
* MyCli主函数
*/
public static void main(String[] args) throws ClassNotFoundException, IOException {
runMyCli(new CliContext(System.in, System.out, System.err, ExitType.SYSTEM_EXIT), args);
}
public static void runMyCli(CliContext ctx, String[] args)
throws ClassNotFoundException, IOException {
// ... 参数解析、连接数据库等逻辑
// 核心功能:读取并执行指定文件中的所有SQL语句
executeSqlFile(ctx, filePath);
}
/**
* 执行SQL文件中的所有SQL语句
*/
private static void executeSqlFile(CliContext ctx, String filePath) {
// ... 文件检查、读取SQL、建立连接
// 循环执行所有SQL语句
executeAllSqlStatements(ctx, connection, sqlStatements);
}
// ... 其他辅助方法(读取文件、执行单条SQL等)
}
代码较长,此处仅展示关键结构和注释,完整实现需参考原文或自行补充。
运行MyCli.java前,需要在IDE中配置运行参数,至少包含-u root -pw root -sql_dialect table,以指定用户名、密码和使用表模型。启动后,程序会提示输入SQL文件路径,输入test_sql.txt即可开始自动执行。

程序会逐条显示SQL执行状态,正常情况下所有语句都会执行成功。
四、执行SQL查询验证数据
数据写入后,我们可以启动标准的Cli.java进行查询验证。执行以下查询语句:
select * from demo_test.t1 where time between 1998 and 2002;

为了分析查询的执行细节,可以使用explain analyze命令:
explain analyze select * from demo_test.t1 where time between 1998 and 2002;

从分析结果可以看到,DeviceTableScanNode在读取文件时,其Next函数被调用了两次,这可能是由于加载了两个Page的数据所致,与之前设置的Page点数上限(1000)和查询的时间范围是吻合的。
五、分析生成的TsFile文件
MyCli执行完成后,数据会持久化到TsFile中。在项目目录的data文件夹下可以找到生成的.tsfile文件。由于我们插入的数据时间戳是有序的,因此只会生成顺序文件(无序数据会同时产生顺序文件和乱序文件)。

为了深入理解存储结构,我们可以使用IoTDB自带的TsFileSketchTool工具分析文件。为了方便,可以直接修改其main方法中的文件路径参数并运行。
public class TsFileSketchTool {
public static void main(String[] args) throws IOException {
// 注释掉原有的参数检查,直接指定文件路径
String filename = "./data/datanode/data/sequence/demo_test/26/0/1765198758289-1-0-0.tsfile";
String outFile = "ts_file_sketch.txt";
System.out.println("TsFile path:" + filename);
System.out.println("Sketch save path:" + outFile);
new TsFileSketchTool(filename, outFile).run();
}
}
运行该工具后,会输出详细的文件结构信息。通过分析输出,我们可以验证之前的操作效果,这对进行大数据存储性能调优很有帮助:
- 列与Chunk:表
t1有5个列(包含隐式的时间戳列),每个列对应一个Chunk。
- Page分割:每个Chunk下包含多个Page。例如
id列被分成了32个Page,每个Page恰好存储1000个数据点,这证实了set configuration "max_number_of_points_in_page" = '1000';设置已生效。
- 统计信息:每个Page都存储了该页数据的最小值、最大值、开始时间、结束时间等统计信息,即使是
STRING类型的district列也是如此。这些统计信息用于查询时的快速过滤。
TsFile path:./data/datanode/data/sequence/demo_test/26/0/1765198758289-1-0-0.tsfile
Sketch save path:ts_file_sketch.txt
-------------------------------- TsFile Sketch --------------------------------
file path: ./data/datanode/data/sequence/demo_test/26/0/1765198758289-1-0-0.tsfile
file length: 178058
...
||||||||||||||||||||| [Chunk Group] of t1, num of Chunks:5
...
12| [Chunk] of t1.id, startTime: 1 endTime: 32000 count: 32000 [minValue:1,maxValue:32000,firstValue:1,lastValue:32000,sumValue:512016000]
| [chunk header] marker=65, measurementID=id, dataSize=3455, dataType=INT32, compressionType=LZ4, encodingType=TS_2DIFF
| [page-1] UncompressedSize:257, CompressedSize:62, startTime: 1 endTime: 1000 count: 1000 [minValue:1,maxValue:1000,firstValue:1,lastValue:1000,sumValue:500500]
| [page-2] UncompressedSize:257, CompressedSize:63, startTime: 1001 endTime: 2000 count: 1000 [minValue:1001,maxValue:2000,firstValue:1001,lastValue:2000,sumValue:1500500]
| ... (共32个Page)
...
---------------------------------- TsFile Sketch End ----------------------------------
通过以上步骤,我们完成了从数据生成、写入、查询验证到存储文件分析的完整流程,有助于开发者深入掌握IoTDB表模型的数据处理与存储原理。