查询结果不符合预期的常见原因

在使用Lindorm宽表引擎的过程中,如果使用方法不当,可能会出现查询结果与预期不符的情况。本文列举了导致此类问题产生的几种常见原因,请您根据原因进行排查,优化查询条件。

问题描述

Lindorm宽表引擎是兼容HBase的NoSQL数据引擎,其存储模型是基于LSM-Tree实现的。在执行写入操作时,数据会被先写入预写日志(Write-Ahead-Log,WAL)中,再写入数据库。只要写入过程中没有出现报错,相应的数据一定会写入成功,即使遇到机器宕机等情况,都可以通过恢复WAL的方式恢复数据,保障数据的持久性,不会出现数据写入后非预期不可见的情况。但是由于宽表引擎的特性比较多,例如数据版本号、时间戳、TTL过期等复杂特性,如果使用不当,则可能出现写入的数据无法覆盖、被过期删除等无法被查询到或查询不符合逾期的情况,常见原因如下:

常见原因

数据未正常写入或查询发起的时间在数据写入前

在Lindorm宽表引擎中,数据写入是即时可见的,并不会出现写入后过一段时间才可见的情况。

Lindorm宽表经常会被应用在大数据链路中,如果写入链路出现问题,则可能导致写入延迟或无法正常写入数据,此时进行数据查询,该行数据还未写入,因此会产生无法查询到数据的情况。

如果您在使用中遇到数据写入一段时间后才能查到的情况,建议您在查询条件中添加相关HINT参数,指定查询结果中返回数据写入的时间戳,根据该时间戳判断是否出现了查询先于写入的情况。如何返回时间戳,请参见多版本数据管理

说明

如果写入数据时未指定时间戳,则返回的时间戳代表这行数据写入的时间。

STRING字段中含有非正常的停止符或不可见字符

在查询数据类型为STRING的字段时,如果STRING字段中包含不可见字符,则可能造成查询结果不符合逾期的现象。假设订单列orderID为STRING类型,由于程序bug等原因,在数据写入时加入了不可见字符,比如订单号1000写成了1000(不可见字符),即1000后跟有不可见字符,那么在使用查询条件

where oderID="1000"时,这个包含不可见字符的字段是查询不到的,可以使用where orderID > "1000" limit 1来确认是否存在类似问题。

此外,Lindorm不支持STRING字段的中间包含停止符(结尾有停止符是正常STRING字段)。假设写入数据为“1000\停止符\1000”,可能会造成编码异常,导致无法被查询到。

查询条件中列名填写错误

可能包含以下两种情况:

  • 列名大小写错误:Lindorm的列名是大小写敏感的,因此在查询时需注意查询条件中的列名与实际写入的列名大小写是否一致。

  • 未指定列簇(column family):Lindorm宽表是支持多列簇的,如果在建表时未指定family,则写入的列默认被添加至名为f的family下,在数据查询时无需在查询条件中指定family。但如果使用了多family功能,则必须在查询条件中指定family,否则系统将默认查询f这个列簇下的数据,可能导致查询结果与逾期不符。例如,创建了一个名为meta的family,并在meta下写入了一个列column1,则在查询column1中的数据时,必须指定family为meta,例如where meta:column1=xxx;如果未指定family,例如where column1=xxx,系统将默认查询f列簇下的column1,导致查询结果与实际想要得到的结果不一致。

表属性设置了TTL,查询时数据已过期

宽表引擎支持通过设置表属性TTL来指定数据过期时间,TTL的单位是秒(s)。同时,宽表引擎也支持在写入数据时指定时间戳,时间戳的单位为毫秒(ms)。

如果写入数据时未指定时间戳,数据将在指定的TTL时长后过期。例如,指定TTL的值为一天(86400秒),那么今天写入的数据将在明天过期被清理,无法被查询到。

如果写入数据时指定了较早的时间戳,并且该时间戳与当前时间的差值大于TTL设定值,那么可能在写入时就被清理掉,导致数据写入后无法被查询到。

重要

宽表引擎中的KV版本号等同于时间戳。如果您在时间戳的使用上未遵循时间语义,而是使用自定义的版本号(例如1、2、3、4这种比较小的数字),那么在表属性设置了TTL的情况下,数据极易被过期清理。同理,使用较大的自定义时间戳/版本号,例如误将KV的时间戳设置成微秒时间戳或者纳秒时间戳,则可能造成数据无法被正常过期清理。

您可以通过集群管理系统查看TTL的值是否合适,具体步骤:在集群管理系统的概览页面,单击目标数据库下的目标表名。在当前详情表格区域,单击查看表属性,查看TTL参数的值。如何进入集群管理系统,请参见登录集群管理系统

如果您需要修改TTL属性的值,可以通过以下方式:

设置了Cell TTL,查询时数据已过期

宽表引擎支持在某个键值对(Key-Value,KV)上设置TTL属性,用于控制每个KV的过期时间,即Cell TLL,单位为毫秒(ms)。

如果KV上设置了Cell TTL,则其过期时间为min{Cell上设置的过期时间,表属性上的过期时间}。过期时间的判断是根据KV的时间戳或版本号来计算的,如果KV的时间戳过大或过小,都有可能造成数据提早被清理或一直无法被清理,导致查询结果不符合预期。

如果同一列中存在带有Cell TTL的KV1和没有带Cell TTL的KV2,在数据过期前,查询数据时可能会读取到时间戳已更新的KV1。在数据过期后,KV1将被清理,此时查询数据可能会读取到KV2,导致查询结果不符合预期。

说明

是否能读到KV2,取决于设置的表属性VERSIONS,同时还取决于到KV1和KV2是否在不同的文件中、KV1和KV2有没有被Major Compaction操作合并。例如表的VERSIONS设置为1,文件合并发生后,由于只保留一个版本,KV2会在Major Compaction操作中被删除,因此即使KV1被过期清理,查询数据时也无法读取到KV2。

删除请求的时间戳设置不合理

Lindorm宽表引擎支持为删除请求设置时间戳/版本号,代表删除该行/该列在此时间/版本之前的数据,如果未设置时间戳/版本号,默认删除当前时间/当前版本之前的数据。假设未设置时间戳,当前时间为2024年1月16日16:00:00,此时执行DELETE FROM sensor WHERE p1 = 10;表示删除写入时间在2024年1月16日16:00:00之前,且p1为10的一行数据。

如果删除请求的时间戳比数据写入的时间戳/版本号小,那么这行数据不会被删除,此时查询结果中依旧会包含这行数据。

如果删除请求设置的时间戳/版本号较大,删除请求提交后将持续生效,此时再写入数据,则数据写入的时间戳比删除请求设置的时间戳小,数据写入后会被立刻删除,导致无法被查询到。

说明

SQL访问方式不支持设置删除时间戳。

链路等问题导致数据写入后立刻被删除

在一些大数据链路中,如果写入和删除发生在的不同的程序或进程中,则可能会出现数据写入后被立刻删除的情况,需要进行排查。

此外,如果您是使用阿里云实时计算Flink并采用Flink SQL方式访问Lindorm宽表,需注意旧版本的Flink Lindorm Connector可能产生的问题:在更新Lindorm表时,如果一条Delete操作和数据写入的时间非常接近,可能会导致写入操作被Delete操作覆盖。您可以通过在Flink中设置ignoreDelete=true来规避该问题。

更多介绍,请参见Flink的Lindorm Conenctor

表属性VERSIONS被设置为0导致数据被删除

宽表的VERSIONS属性的值为0,表示表中的数据不会保留,任何写入的数据都将被删除,无法查询。如果建表时未设置VERSIONS属性,则VERSIONS的值默认为1,即表中的数据仅保留一个版本。更多介绍,请参见多版本数据管理

如果误把VERSIONS属性的值设置为0,建议您删除表并重新建表,或将VERSIONS属性修改为大于等于1的值。

您可以通过集群管理系统查看VERSIONS的值是否为0,具体步骤:在集群管理系统的概览页面,单击目标数据库下的目标表名。在当前详情表格区域,单击查看表属性,查看VERSIONS参数的值。如何进入集群管理系统,请参见登录集群管理系统

如果您需要修改VERSIONS属性的值,可以通过以下方式:

说明

MIN_VERSIONS代表保留的最少版本数,该属性默认为0,不会造成数据写入后无法被查到的情况。

表属性为IMMUTABLE的表有更新

表的属性被设置成IMMUTABLE表示该表仅支持整行写入(即一行的数据通过一条UPSERT语句写入,不支持一行的数据通过多条UPSERT语句写入),不可更新或删除。但在实际操作中,即使表的属性被设置成IMMUTABLE,Lindorm也并不会禁止更新和删除行为,但该类操作会造成索引表和主表的数据不一致,进而导致查询命中索引和命中主表的结果不一致。

建议您重新构建索引表,并停止更新或删除属性为IMMUTABLE的表中的数据。