随着云原生时序数据库(Time Series Database,TSDB) TDengine 3.0 的发布,很多用户其实会好奇:“和 2.0 比起来,3.0 有哪些直观的优化呢?”
本文将以 Update 这一功能的变化为例,具体介绍一下 3.0 的细致优化。
和 2.0 比起来,3.0 版本的 Update 不再需要参数配置,“部分列更新”成为数据库本身的特性,并且还解决了 2.0 的 Update 机制的一些不足。
一、2.0 时代的 Update:
对于 TDengine 的 Update 功能,老用户应该都比较清楚:
在 2.0 版本中,TDengine 通过对 database 级别参数值 “update” 的配置,来完成对相同时间戳行的数据更新。
- update 为 0 ,不更新;
- update 为 1 ,整行更新,未指定值的列会被更新为 NULL;
- update 为 2,部分列更新,未指定值或者写入 NULL 的列会保留原有值。
一开始,TDengine 只支持 update 为 1 (即整行更新)。但在很多场景下,用户往往不会在同一时间更新一张表中的所有列,因此我们后续又支持了 update 为 2(即部分列更新)。
显然,部分列更新更为灵活,因为它既可以做到整行更新,又可以做到局部更新。但是通过它的逻辑,我们或许会有这样的疑问:如果我想把一个值更新为 NULL 值,应该怎么办呢?这个疑问是有道理的。不过 2.0 版本并不支持这样的更新。
在 2.0 的版本中,数据可以被分为两大类 NORMAL 和 NULL 。 NORMAL 类代表 TDengine 所有数据类型中的非空值字面量,而 NULL 则代表所有数据类型的空值字面量。
当 Update 行为发生时,它的逻辑是 :如果在某一行 insert 语句中没有显式写入某一列值的话,会认为该列输入了 NULL 值。
所以下面这两种写法的语义其实是一样的。
- insert into t1 (ts,num1,num2) values (“xxxx”,1,2) ;
- insert into t1 values (“xxxxx”,1,2,NULL) ;
所以在 update 为 1 时,上述两个 SQL 都会用 NULL 值会覆盖掉 num3 原有的值。
而在 update 为 2 时,上述两个 SQL 又都会保留 num3 原有的值。
因此,上述设定会导致 2.0 版本无法把一个值更新为 NULL 值。
二、3.0 时代的 Update:
为了避免这一问题,从 3.0 版本开始,TDengine 引入了 None 值(未赋值)语义——当用户进行 insert 输入时,如果未对某一列进行显式输入(如上面的 SQL 语句 1),taosc 不再将其置为 NULL,而是将该列标识为 None(对外查询仍显示为 Null)。
因此,仍然以上述两个 SQL 为例,在 3.0 版本的 Update 场景中,它们已经有了不同:
- Insert into t1 (ts,num1,num2) values (“xxxxx”,1,2); (对于 num3 列, 标记为 NONE)
- insert into t1 values (“xxxxx”,1,2,NULL);(对于 num3 列, 标记为 NULL)
而 3.0 的 Update 规则为:如果写入的是 NORMAL/NULL ,那么就取版本号比较大的行,如果我写入的是 NONE,那么就取版本号比较小的行,所以:
- SQL 语句 1 会把 t1 表的 num1 和 num2 列都更新成最值,num3 保持原样(因为 num3 要和 NONE 取版本比较小的行);
- SQL 语句 2 会把 t1 表的 num1 和 num2 列都更新成最值,num3 更新成 null(因为 num3 要和 NULL 取版本比较大的行)。
由上可见,由于 NONE 的引入,3.0 已经可以在无需任何配置的情况下,以部分列更新模式来进行数据更新了。
可以用这几条简单的 SQL 来体验 TDengine 的这个新特性:
CREATE TABLE `t1` (`ts` TIMESTAMP, `num1` INT, `num2` INT, `num3` INT);
insert into t1 values ("2020-01-01 01:00:00",1,1,1);
insert into t1 (ts,num1,num2) values ("2020-01-01 01:00:00",2,2);
insert into t1 values ("2020-01-01 01:00:00",3,3,null);
三、更新逻辑
其实,TDengine 不论是在 2.0 还是 3.0 时代,更新的逻辑都是类似的:3.0 是不论在硬盘还是内存的数据都只是标记更新(写入时间戳相同的数据会生成子数据块),而 2.0 内存中的数据是物理更新,硬盘上是标记更新。在查询的时候,他们都是先把原本的数据块和子数据块从硬盘上读取到内存中,再通过被高版本覆盖或被低版本覆盖的方式完成某列数据的更新或保留,然后再把它们与原先存在于内存中的数据合并处理,最后把结果返回给客户端。
不过,与 2.0 比起来,3.0 使用了 multi-version (多版本模式),这是由于 2.0 的更新、删除功能不是一开始就有的,而是后期逐步增加的实现,有点像打补丁,不够优雅。所以 3.0 相当于把这些割裂的功能,从存储引擎底部做了一次优化融合,也大幅提升了用户体验,以这个 Update 功能为例,用户不再需要任何配置,只需要知道 TDengine 的更新方式就可以了。
而关于 3.0 的存储引擎的底层优化具体详情,可以通过这篇文章进一步了解。
四、结语
3.0 上有很多类似这样的优化和重构。之后我们会持续以文章视频等形式,带领大家深入探索这款云原生的时序数据库,一起为企业带来价值,为开发者带来成功。