Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

data insertion succeeded but field value is null if remove a field then add it back during data insertion #3818

Closed
quchunhui opened this issue Oct 12, 2020 · 5 comments
Assignees
Labels
bug Something isn't working

Comments

@quchunhui
Copy link

Bug Description
【现象1】
写了一个模拟长连接,并持续插入数据的java代码样例,代码请参考后面粘贴的部分。测试过程中发现一个问题
1、在代码持续运行的过程中(While循环插入的过程中),删除其中任意一个字段(alter table st_device_data_up drop column di_updownnvalopovt;),此时数据仍可以正常插入。
2、尝试将删除的字段恢复(alter table st_device_data_up add column di_updownnvalopovt double;),此时数据扔可以正常插入。
3、查询字段(如select time, ai_pick_udata, di_updownnvalopovt, productKey, deviceName from st_device_data_up order by time desc limit 10;),则发现被删除在恢复的字段di_updownnvalopovt,一直显示的值是null。程序也没有报错。

【现象2】
4、继续上面的操作,在java代码持续运行的过程中,删除掉超级表其中任意一个字段。程序如果一直运行,则一直不会报错。
5、重新启动程序,则会提示字段不匹配而插入失败。

To Reproduce
1、按照下面的建表语句创建超级表及表
2、编写java的代码,如下面的样例(省略了JDBC连接的部分)
3、运行java代码(while一直循环,每5秒执行一次插入)
4、删除超级表中的字段(如di_updownnvalopovt)
5、恢复字段
6、删除另一个字段
7、停止java程序
8、重新启动java程冠希

Expected Behavior
不清楚什么结果算是正确的。

Screenshots
不知道如何插入图片,没贴图哈,见谅。

Environment (please complete the following information):

  • OS: Linux Centos8
  • Memory, CPU, current Disk Space:16GB、8Core、100GBDist
  • TDengine Version:2.0.5.0

超级表建表语句
create table if not exists device_data_up(
time timestamp,
AI_PICK_UDATA double,
AI_PROD_NPER double,
AI_PROD_NPICK double,
AI_PRODUCT_EFF double,
AI_PRODUCT_QUANTITY double,
AI_PRODUCT_TIME_MIN double,
AI_PROD_NPRODING double,
DI_BELOWCLOSTS double,
DI_BELOWOPNSTS double,
DI_CANDY_OUTBEELINEA double,
DI_CANDY_OUTBEELINEB double,
DI_CANDY_OUTBEELINEC double,
DI_CHUCKCLOSTS double,
DI_CHUCKOPNSTS double,
DI_COVERCLOSTS double,
DI_COVEROPNSTS double,
DI_CTNCLO_BSTART double,
DI_CTNOPN_BSTART double,
DI_DENSO_BAUTOST double,
DI_DENSO_BERROR double,
DI_DENSO_BMOTORONST double,
DI_DENSO_BREADY double,
DI_DENSO_BRUNNING double,
DI_EPSON_BERROR double,
DI_EPSON_BESTOPON double,
DI_EPSON_BMOTORONST double,
DI_EPSON_BREADY double,
DI_EPSON_BRUNNING double,
DI_LEFTPUSHCLOSTS double,
DI_LEFTPUSHOPNSTS double,
DI_PRINT_BRUNNING double,
DI_RIGHTCLOSTS double,
DI_RIGHTOPNSTS double,
DI_RIGHTPUSHCLOSTS double,
DI_RIGHTPUSHLOPN double,
DI_RLCLOSTS double,
DI_RLOPNSTS double,
DI_ROBOT1_BLINKSTS double,
DI_ROBOT1_BSTART double,
DI_ROBOT2_BLINKSTS double,
DI_ROBOT2TOSTOR double,
DI_ROBOT2TOWAI double,
DI_SYS_BRUNNING double,
DI_TOPCLOSTS double,
DI_TOPOPNSTS double,
DI_UPDOWNCLOSTS double,
DI_UPDOWNOPNSTS double,
DI_BBELTSENSOROVT double,
DI_BCTNCLOCOOPNOVT double,
DI_BCTNCLORCLOOVT double,
DI_BCTNCLOROPNOVT double,
DI_BCTNCLOTCLOOVT double,
DI_BCTNCLOTOPNOVT double,
DI_BCTNOVT double,
DI_BELOWVALCLOOVT double,
DI_BELOWVALOPNOVT double,
DI_BROBOT2VALCLOVT double,
DI_BROBOT2VALOPOVT double,
DI_BRTSUCKEROVT double,
DI_BVALCOVERCLOOVT double,
DI_CHUCJVALCLOOVT double,
DI_CHUCJVALOPOVT double,
DI_CHUCKVALOPNOVT double,
DI_DENSOBERROR double,
DI_EPSONBERROR double,
DI_INSENCTNALARM double,
DI_LEFTNEWVALCLOVT double,
DI_RLNEWOPVALOPOVT double,
DI_RLNEWVALCLOOVT double,
DI_UPDOWNNVALCLOVT double,
DI_UPDOWNNVALOPOVT double)
tags (productKey nchar(256), deviceName nchar(256))

java代码
package com.rexel.tdengine.api;

import com.alibaba.fastjson.JSONObject;
import com.rexel.tdengine.utils.TdUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.Date;

/**

  • @classname CreateDatabase

  • @description CreateDatabase

  • @author: chunhui.qu

  • @Date: 2020/10/12
    */
    public class InsertJson {
    public static void main(String[] args) throws SQLException {
    TdUtils tdUtils = TdUtils.getInstance();
    Connection conn = tdUtils.getConnection();
    if (conn == null) {
    return;
    }
    System.out.println("get connection");

     Statement stmt = conn.createStatement();
     if (stmt == null) {
         return;
     }
    
     String demoStr = "{\n"
         + "    \"table\":\"device_data_up\",\n"
    

// + " "time":1585221653319,\n"
+ " "tags":{\n"
+ " "productKey":"a1B6t6ZG6oR",\n"
+ " "deviceName":"QCHTestDevice1"\n"
+ " },\n"
+ " "data":{\n"
+ " "AI_PICK_UDATA":11,\n"
+ " "AI_PROD_NPER":12,\n"
+ " "AI_PROD_NPICK":13,\n"
+ " "AI_PRODUCT_EFF":13,\n"
+ " "AI_PRODUCT_QUANTITY":0,\n"
+ " "AI_PRODUCT_TIME_MIN":0,\n"
+ " "AI_PROD_NPRODING":0,\n"
+ " "DI_BELOWCLOSTS":0,\n"
+ " "DI_BELOWOPNSTS":0,\n"
+ " "DI_CANDY_OUTBEELINEA":0,\n"
+ " "DI_CANDY_OUTBEELINEB":0,\n"
+ " "DI_CANDY_OUTBEELINEC":0,\n"
+ " "DI_CHUCKCLOSTS":0,\n"
+ " "DI_CHUCKOPNSTS":0,\n"
+ " "DI_COVERCLOSTS":0,\n"
+ " "DI_COVEROPNSTS":0,\n"
+ " "DI_CTNCLO_BSTART":0,\n"
+ " "DI_CTNOPN_BSTART":0,\n"
+ " "DI_DENSO_BAUTOST":0,\n"
+ " "DI_DENSO_BERROR":0,\n"
+ " "DI_DENSO_BMOTORONST":0,\n"
+ " "DI_DENSO_BREADY":0,\n"
+ " "DI_DENSO_BRUNNING":0,\n"
+ " "DI_EPSON_BERROR":0,\n"
+ " "DI_EPSON_BESTOPON":0,\n"
+ " "DI_EPSON_BMOTORONST":0,\n"
+ " "DI_EPSON_BREADY":0,\n"
+ " "DI_EPSON_BRUNNING":0,\n"
+ " "DI_LEFTPUSHCLOSTS":0,\n"
+ " "DI_LEFTPUSHOPNSTS":0,\n"
+ " "DI_PRINT_BRUNNING":0,\n"
+ " "DI_RIGHTCLOSTS":0,\n"
+ " "DI_RIGHTOPNSTS":0,\n"
+ " "DI_RIGHTPUSHCLOSTS":0,\n"
+ " "DI_RIGHTPUSHLOPN":0,\n"
+ " "DI_RLCLOSTS":0,\n"
+ " "DI_RLOPNSTS":0,\n"
+ " "DI_ROBOT1_BLINKSTS":0,\n"
+ " "DI_ROBOT1_BSTART":0,\n"
+ " "DI_ROBOT2_BLINKSTS":0,\n"
+ " "DI_ROBOT2TOSTOR":0,\n"
+ " "DI_ROBOT2TOWAI":0,\n"
+ " "DI_SYS_BRUNNING":0,\n"
+ " "DI_TOPCLOSTS":0,\n"
+ " "DI_TOPOPNSTS":0,\n"
+ " "DI_UPDOWNCLOSTS":0,\n"
+ " "DI_UPDOWNOPNSTS":0,\n"
+ " "DI_BBELTSENSOROVT":0,\n"
+ " "DI_BCTNCLOCOOPNOVT":0,\n"
+ " "DI_BCTNCLORCLOOVT":0,\n"
+ " "DI_BCTNCLOROPNOVT":0,\n"
+ " "DI_BCTNCLOTCLOOVT":0,\n"
+ " "DI_BCTNCLOTOPNOVT":0,\n"
+ " "DI_BCTNOVT":0,\n"
+ " "DI_BELOWVALCLOOVT":0,\n"
+ " "DI_BELOWVALOPNOVT":0,\n"
+ " "DI_BROBOT2VALCLOVT":0,\n"
+ " "DI_BROBOT2VALOPOVT":0,\n"
+ " "DI_BRTSUCKEROVT":0,\n"
+ " "DI_BVALCOVERCLOOVT":0,\n"
+ " "DI_CHUCJVALCLOOVT":0,\n"
+ " "DI_CHUCJVALOPOVT":0,\n"
+ " "DI_CHUCKVALOPNOVT":0,\n"
+ " "DI_DENSOBERROR":0,\n"
+ " "DI_EPSONBERROR":0,\n"
+ " "DI_INSENCTNALARM":0,\n"
+ " "DI_LEFTNEWVALCLOVT":0,\n"
+ " "DI_RLNEWOPVALOPOVT":0,\n"
+ " "DI_RLNEWVALCLOOVT":0,\n"
+ " "DI_UPDOWNNVALCLOVT":0,\n"
+ " "DI_UPDOWNNVALOPOVT":0\n"
+ " }\n"
+ "}";
JSONObject demoJson = JSONObject.parseObject(demoStr);

    while(true) {
        String sql = jsonToSql(demoJson);
        stmt.executeUpdate(sql);

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

private static String jsonToSql(JSONObject jsonObject) {
    String sql = "insert into {table} ({fields}) values ({values});";
    sql = sql.replace("{table}", getTable(jsonObject));
    sql = sql.replace("{fields}", getFields(jsonObject));
    return sql.replace("{values}", getValues(jsonObject));
}

private static String getTable(JSONObject jsonObject) {
    return "t_" + jsonObject.getString("table");
}

private static String getTime(JSONObject jsonObject) {
    if (jsonObject.containsKey("time")) {
        return timeLongToStr(jsonObject.getLong("time"));
    } else {
        return timeLongToStr(System.currentTimeMillis());
    }
}

private static String getFields(JSONObject jsonObject) {
    JSONObject dataJson = jsonObject.getJSONObject("data");
    StringBuilder sb = new StringBuilder();
    sb.append("time").append(",");
    dataJson.forEach((key, value) -> {
        sb.append(key).append(",");
    });
    return sb.substring(0, sb.length() - 1);
}

private static String getValues(JSONObject jsonObject) {
    JSONObject dataJson = jsonObject.getJSONObject("data");
    StringBuilder sb = new StringBuilder();
    sb.append("\"").append(getTime(jsonObject)).append("\"").append(",");
    dataJson.forEach((key, value) -> {
        sb.append(value).append(",");
    });
    return sb.substring(0, sb.length() - 1);
}

private static String timeLongToStr(long time) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    return sdf.format(new Date(time));
}

}

@quchunhui quchunhui added the bug Something isn't working label Oct 12, 2020
@quchunhui quchunhui changed the title 不间断插入的过程中,删除表字段,插入正常。程序停止再启动之后,插入异常。 不间断插入的过程中,删除表字段,然后再回复字段,数据插入正常,查询被删除字段结果为null。 Oct 12, 2020
@sangshuduo
Copy link
Contributor

@hjxilinx 看一下

@localvar localvar changed the title 不间断插入的过程中,删除表字段,然后再回复字段,数据插入正常,查询被删除字段结果为null。 data insertion succeeded but field value is null if remove a field then add it back during data insertion Oct 13, 2020
@hjxilinx
Copy link
Contributor

背景:TDengine 在设计中支持了多版本schema 数据写入机制,即允许不同版本的 schema 的数据进行写入,字段使用 ID 标识,字字段 ID 单调递增。

现象1】
写了一个模拟长连接,并持续插入数据的java代码样例,代码请参考后面粘贴的部分。测试过程中发现一个问题
1、在代码持续运行的过程中(While循环插入的过程中),删除其中任意一个字段(alter table st_device_data_up drop column di_updownnvalopovt;),此时数据仍可以正常插入。

我们假设最开始的 schema版本为1, 在您删除一个字段以后,管理节点的表的 schema 更新为2,但是由于写入程序在本地有 schema 的缓存,所以仍然能够正常解析老版本数据(schema 版本1)并进行正常的写入。同时,数据节点仍然能够接收老版本(schema版本1)数据的写入(数据节点最大能够接收8个不同的合法版本进行数据写入)。所以您看到的写入是完全正常没有任何错误。

2、尝试将删除的字段恢复(alter table st_device_data_up add column di_updownnvalopovt double;),此时数据扔可以正常插入。

再次建立同名称字段(注意:这里增加的字段不是恢复,TDengine 不支持恢复字段,所以虽然名称一样,但是这是一个全新的字段,因为内部给该字段分配新的 ID),此时管理节点的 schema 版本更新为3,但是由于缓存的存在,客户端程序仍然在使用schema版本1进行数据写入。

3、查询字段(如select time, ai_pick_udata, di_updownnvalopovt, productKey, deviceName from st_device_data_up order by time desc limit 10;),则发现被删除在恢复的字段di_updownnvalopovt,一直显示的值是null。程序也没有报错。

当您开一个客户端进行查询的时候,由于客户端本地此时并没有 schema 缓存,所以主动去管理节点请求 schema,管理节点将 schema 版本3给新的客户端。此时,客户端并不知道版本1和版本2的存在。您输入的查询使用了最新的schema 版本。由于仍在写入的客户端压根不知道您新建的字段(它只存在于 schema 版本3中),所以您查询的该字段是 null。

【现象2】
4、继续上面的操作,在java代码持续运行的过程中,删除掉超级表其中任意一个字段。程序如果一直运行,则一直不会报错。

如上所述,由于缓存的存在,一个合法的历史 schema 可以持续进行写入。

5、重新启动程序,则会提示字段不匹配而插入失败。

如果您重启的写入程序,这个时候由于本地的schema 被清空。在第一次解析 SQL 语句的时候主动向管理节点请求 schema,管理节点将 schema 版本4(最新的 schema,因为你又删除了一个字段)返回给客户端。这个时候, 版本4的 schema 与当前 SQL语句中指定的字段不匹配,所以写入程序保存。

如果你不删除最后的那个字段,只是“恢复”了之前删除的字段(此时是scheme 版本3),您会发现程序还能正常运行,并且重启后该字段的值能够写入到系统中。

To Reproduce
1、按照下面的建表语句创建超级表及表
2、编写java的代码,如下面的样例(省略了JDBC连接的部分)
3、运行java代码(while一直循环,每5秒执行一次插入) [schema 版本1]
4、删除超级表中的字段(如di_updownnvalopovt) [schema 版本2]
5、恢复字段 [schema 版本3]
6、删除另一个字段 [schema 版本4]
7、停止java程序
8、重新启动java [java 程序的使用 schema 版本3(1)系统的模式,写入失败]

最后补充一点,您可能会奇怪为什么要这样设计。因为TDengine 希望能够对于应用系统升级的平滑支持(新增物联网设备采集的字段和写入内容不影响未升级的老设备的写入)。 希望在多个不同的客户端在不需要全部同步重启的情况下就能够进行正常的数据写入。因为物联网设备全部同时更新写入程序比较困难。

希望我解释清楚了,如果有问题欢迎留言。: )
@quchunhui

@quchunhui
Copy link
Author

根据您的回答,我理解的是:
tdengine的schema字段实际上是只增不减的,程序总为每一个字段赋予了id,虽然名字一样,实则已经不是同一个字段了。
如果我这个理解正确,这个issue就可以关闭了。
我会在使用的过程中,考虑td的使用方式。。

@quchunhui
Copy link
Author

非常感谢您特别详细的回答。

@quchunhui
Copy link
Author

关闭

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants