全部产品

高级特性

本文介绍如果通过HBase增强版SQL来使用HBase的一些NoSQL特性,如多版本、时间戳、schema-free等,同时,也介绍一些增强版中独有的高级特性。

多版本与时间戳

在HBase中,一行中某一列的数据是可以有多个版本的(列级别的多版本)。版本通过时间戳来描述。因此,可以有如下几个操作:

  • 指定时间戳写入数据
  • 读取指定版本数的数据,如:读取最近5个版本,或者读取所有版本
  • 读取指定时间戳范围内的数据,如:读取所有时间戳大于t1的版本,读取[t1, t2]范围内的版本
  • 版本数与时间戳范围结合:如:读取[t1, t2]范围内的最新的5个版本

标准SQL中是没有多版本和时间戳的概念的,因此,我们需要对标准SQL做一些扩展来支持。

  • 主键列是没有多版本或时间戳的概念的,只有非主键列才有
  • 多版本是列级别的,但是在SQL中一列是不能有多个值的,因此,在SQL中会将列级多版本展开为行级。

引入两个系统列名来描述时间戳和版本数:

_l_ts_:表示时间戳,单位:ms,毫秒_l_versions_:表示版本数,用于select指定要最多读取多少个版本

下面看一些示例:

  1. -- 创建支持多版本的表,最多保留5个版本
  2. create table dt (p1 integer, p2 integer, c1 integer, c2 integer,
  3. constraint pk primary key(p1, p2 desc)) 'VERSIONS'='5';
  4. -- 指定时间戳写入数据,时间戳的单位是ms,毫秒
  5. -- c1c2列的时间戳均为15367736217189
  6. upsert into dt (p1,p2,c1,c2, _l_ts_) values (1,2, 10, 20, 15367736217189);
  7. -- 删除指定行中时间戳小于或等于15367736217189的所有版本
  8. delete from dt where p1 = 1 and p2 = 2 and _l_ts_ = 15367736217189;

查询场景示例:

  1. -- 创建不支持多版本的表,即默认只保留一个版本
  2. create table dt (p1 integer, p2 integer, c1 integer, c2 integer,
  3. constraint pk primary key(p1, p2 desc)) 'VERSIONS'='5' ;
  4. -- 以指定时间戳的方式写入一行数据,每个非主键列都有4个版本
  5. -- 为便于展示,这里使用100200这种在视觉上有区分度的时间戳
  6. upsert into dt (p1,p2,c1,c2, _l_ts_) values (1,2, 10, 20, 100);
  7. upsert into dt (p1,p2,c1,c2, _l_ts_) values (1,2, 30, 40, 200);
  8. upsert into dt (p1,p2,c1,c2, _l_ts_) values (1,2, 50, 60, 300);
  9. upsert into dt (p1,p2,c1,c2, _l_ts_) values (1,2, 70, 80, 400);
  10. -- 以指定时间戳的方式写入一行数据,但不同列的时间戳不同
  11. upsert into dt (p1,p2,c1,c2, _l_ts_) values (3,4, 70, 80, 500);
  12. upsert into dt (p1,p2,c1, _l_ts_) values (3,4, 90, 600);
  13. upsert into dt (p1,p2, c2, _l_ts_) values (3,4, 90, 700);
  14. -- 查询dt的所有行的所有版本
  15. select * from dt where _l_versions_ = 5

查询结果如下:

null

  • 每一个非主键列都有自己的时间戳,列名为c1_l_ts_c2_l_ts_,即原始列名拼接上_l_ts_
  • 当一行中不同的非主键列有不同的时间戳时,会在多行中显示他们,而不是放在同一行里。缺失的列则置为null


  1. -- 每列最多显示两个版本
  2. select * from dt where _l_versions_ = 2;

null


  1. -- 查询时间戳在[200, 500]之间的所有版本
  2. select * from dt where p1 = 1 and p2 = 2 and _l_ts_ >= 200 and _l_ts_ < 500
  3. and _l_versions_ = 5;

null


  1. -- 查询时间戳在[200, 500]之间的最新的一个版本
  2. select * from dt where p1 = 1 and p2 = 2 and _l_ts_ >= 200 and _l_ts_ < 500;

null可见在默认情况下,只读取最新的一个版本。如果希望读取多个版本,需要显式的指定l_versions的值。

注意:多版本是有额外的存储与运行时开销的。请先明确业务场景后再使用。

动态列

Schema-free是HBase的强大能力,使的每一行都可以有完全不同的列,用户不需要预先定义好一套schema,也不需要担心业务schema发生变化时,需要alter table的问题。而SQL标准是要求schema的,所以,我们需要引入一些约束来让SQL支持schema-free特性。

建表,需要显式设置表是支持动态列的,'DYNAMIC_COLUMNS' = 'true',例如:

  1. create table dt (p1 integer, p2 integer, c1 integer, c2 integer,
  2. constraint pk primary key(p1, p2 desc)) 'DYNAMIC_COLUMNS' = 'true';

动态列由于没有出现在schema里,所以,在使用时需要显式“告诉”SQL这个列的实际数据类型是什么。Phoenix便采用了这个方案。而HBase增强版则采用了一个折中的策略,即强制约束动态列必须是varbinary类型。这样,SQL与未使用动态列时没有语法上的差别。例如:

  1. upsert into dt(p1, p2, d1, d2) values(1, 2, '5A6B77FF', '12345');
  2. -- dtschema中的仅有4列,p1,p2,c1,c2,这里写入了两个动态列d1,d2,并为其写入了16进制编码的数据
  3. -- 查询没有限制
  4. select * from dt;
  5. select d1 from dt;

注意:动态列只能是非主键列,主键列在表建好后是无法修改的(目前不开放修改主键的能力)。