Phoenix系列|创建Phoenix映射表
目前,在公司小部分的业务场景中有用到 Phoenix,但也仅限基于 Phoenix 的二级索引机制来进行查询上的优化。虽然使用的频次不大,但偶尔用到时,有些语句的使用方式和注意事项总记不太熟,每次都需要一顿谷歌和百度,然后从五花八门的文章中摄取着自己需要的信息。
本文是 Phoenix 系列的篇文章,我打算从 Phoenix 映射表的创建开始说起。Phoenix 映射表的创建虽然看起来很简单,但其中的细节和潜在的坑着实也不少,且容我慢慢道来。
本文详细记录了 Phoenix 映射表的基本创建方式,以及我自己额外的一些思考和测试,如果文章中有描述不当的地方,还望大家多多指教,也欢迎大家在留言区踊跃发言。
1. 我的软件环境
在开始正式介绍之前,还是先来说明下我 Phoenix 的版本。文中涉及到的关于 Phoenix 的使用小示例,基于的版本分别是PHOENIX-4.14.0-cdh5.13.2
和PHOENIX-5.0.0-cdh6.2.0
,其中两个 Phoenix 对应的 HBase 的版本分别是HBase 1.2.0-cdh5.13.1
和HBase 2.1.0-cdh6.3.1
。如果没有特别的说明,所选示例在两个版本中运行方式以及结果是一样的。
2. 关于 Phoenix
Phoenix 是一款的基于 HBase 的数据库中间件,由 Apache 官方出品,圈内共识,Apache 出品,必属精品,当然,烂尾的除外哈。Phoenix 把用户输入的 SQL 语句翻译成 HBase 底层的查询 API,简化了 HBase 客户端的查询方式,让用户可以使用标准的 SQL 语句来查询 HBase 表里的数据。配合 Phoenix 的二级索引机制,Phoenix 可以实现更加方便和复杂的 HBase 数据查询场景。有关 Phoenix 更深入的介绍,可以前往其官网查看。http://phoenix.apache.org/
3. 在 Phoenix 中创建不存在的 HBase 表的映射表
Phoenix 创建表的语句与 MySql 的语法有点相似,但需要关注的细节也有很多。
create table "user"(
" id" VARCHAR PRIMARY KEY,
"info"."name" VARCHAR,
"info"."age" VARCHAR)
column_encoded_bytes=;
把上述建表语句 copy 到 Phoenix 的 shell 下,运行后查看我们创建的表的信息。
同时,在 HBase 的 default 命名空间中,也自动创建出了一张名叫 user 的表。
跟以普通方式创建的 HBase 表相比,除了常规的表描述信息之外,Phoenix 创建的表还多了一些协处理器等的描述信息。望文生义,这些协处理器大致与 Scan、索引创建等等操作有关。
让我们再来解读下这个简单的建表语句,“user”是表名,id 作为主键,也即是 HBase 表中的 RowKey,“info”终在 HBase 中体现为表的列簇,“name","age"就是表的字段名,紧随其后的是列的类型,统一都是 VARCHAR,也即字符串,字段名默认可以为空,如果强制不为空,则为其加上 NOT NULL。"id"因为要作为 RowKey,所以一定不能是空的。后一行 column_encoded_bytes=0 表示需要禁用列映射规则,否则会降低查询性能,所以一般情况下这里都置为 0。
到这里大家也许会发现,表名、字段名、列簇名都被引号包括,如果我不指定引号呢?让我们先删除表,然后重新运行如下的建表语句:
删除表的操作是 drop table "user",如果待删除的表不存在,就会抛出异常,如果想要避免表不存在的异常,则需要加上表是否存在的判断,即运行 drop table if exists "user"。删除表的同时,底层映射的 HBase 表也一同被删除,这是你删库跑路的利器,所以生产环境下还是谨慎使用吧。
create table user(
id VARCHAR PRIMARY KEY,
info.name VARCHAR,
info.age VARCHAR)
column_encoded_bytes=;
然后查看下我们重新创建的表:
首先我们的表名被转成了大写,再 select 下数据。
查看下 HBase 表:
如上图所示,引号的作用应该很明显了吧,Phoenix 中表名、列簇名、字段名如果不加引号,会被默认转成大写。
4. 在 Phoenix 中创建存在的 HBase 表的映射表
接着,我们删除刚刚创建的 user 表。再来尝试如下的建表方式。首先在 HBase 的命令行下创建一张名叫 user 的表,其列簇是 info,create user,info
。此时 user 表就是一个普普通通的 HBase 表,然后运行如下建表语句:
create table "user"(
id VARCHAR PRIMARY KEY,
info.name VARCHAR,
info.age VARCHAR)
column_encoded_bytes=;
上述建表语句中我故意把 user 加引号,是为了确保在 Phoenix 中创建的表就是小写的名字,而不会在 HBase 中重新生成一张名叫 USER 的表。运行完查看下我们的 HBase 表。多了一个名叫 INFO 的列簇。所以,我们只有严格保证这些大小写一一对应,才能保证 Phoenix 表与 HBase 确立正确一致的映射关系,重新运行如下建表语句。
create table "user"(
" id" VARCHAR PRIMARY KEY,
"info"."name" VARCHAR,
"info"."age" VARCHAR)
column_encoded_bytes=;
所以,我们可以先提前创建 HBase 表,再去创建该表的 Phoenix 映射表,保证大小写一一对应,同样可以达到如小节 3 中的效果。
5. 创建带有 schema 的 Phoenix 表
直接运行如下建表语句:
create table "person"."user"(
" id" VARCHAR PRIMARY KEY,
"info"."name" VARCHAR,
"info"."age" VARCHAR)
column_encoded_bytes=;
如果这个名叫 person 的 schema 不存在就会遇到建表的异常。
运行 create schema "person"来创建 schema,同样需要注意大小写。能正确创建 schema 的前提是需要在 HBase 的服务端和 Phoenix 的客户端增加如下配置:
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
<property>
<name>phoenix.schema.mapSystemTablesToNamespace</name>
<value>true</value>
</property>
schema 创建完成之后,我们的表就能被正常创建了,Phoenix 的表创建完毕后,HBase 中会自动创建名为 person 的 namespace 和名为 person:user 的表。
6. 创建 Phoenix 表指定预分区
默认情况下创建的 Phoenix 表映射的 HBase 底层表只有一个分区,但是如果你的表数据很大,导数据时一个分区能把你急的肝疼。创建 Phoenix 表时预分区的方法有两种,一种是提前创建一个具有预分区的 HBase 表,然后再创建 Phoenix 表来映射已经存在的表;另一种则是创建 Phoenix 表的同时指定好预分区的信息。
示例语句如下:
create table "person"."user"(
"id" VARCHAR PRIMARY KEY,
"info"."name" VARCHAR,
"info"."age" VARCHAR) column_encoded_bytes=
SPLIT ON ('1','2','3','4','5','6','7','8','9');
然后在 HBase 的表监控界面上,你就可以看到如下的分区信息:
还有一种比较糟糕情况是,你的 HBase 表提前创建好了,大量数据也导入了表中,此时你才想起来需要创建 Phoenix 表。在大量数据存在的前提下,在 Phoenix shell 中直接创建映射表,十有八九会遇到客户端超时的异常,貌似不太影响使用,只是不知道会不会数据映射不完整。目前对于这种情况还没找到合适的解决办法,只能在创建表的开始阶段就好好规划下是否需要创建 Phoenix 表以及创建表时的预分区等,尽量避免出现这样的尴尬状况。
7. 各种数据写入后的映射情况
在这一小节里将说明 HBase 的各种写入方式写入的数据能否在 Phoenix 中正常绑定,以及 Phoenix 端数据写入后能否在 HBase 表中顺利查到。
对于 HBase 的数据写入方式,我在这里主要分成 bulkload 和普通的 hbase client api 写入,首先来测试下 client api 的数据写入方式,简单起见我直接在 shell 下 put 一些数据。
再来查看 Phoenix 中的数据,数据正常绑定。
接着测试在 phoenix 表插入一些数据,然后观察 HBase 的数据变化。
upsert into "person"."user" ("id","name","age") values('2', 'leo2','17');
就不贴图了,Phoenix 和 HBase 两边的数据是一一对应的。
那继续来测试 bulkload 的方式导入数据,然后观察 Phoenix 和 HBase 两边的数据情况吧!
测试之前,我们先用truncate_preserve 'person:user'
命令清空 user 表里的数据。
准备一些测试数据,然后上传至 hdfs 的某一个测试目录。
使用如下命令先把 csv 文件转换成 hfile:
hbase org.apache.hadoop.hbase.mapreduce.ImportTsv -Dimporttsv.separator="," -Dimporttsv.bulk.output=/user/leo_jie/user_test_hfile -Dimporttsv.columns=HBASE_ROW_KEY,info:name,info:age person:user /user/leo_jie/user_test.csv
继续运行如下命令,以 bulkload 的方式把数据导入 person:user 表里:
hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles -Dcreate.table=no /user/leo_jie/user_test_hfile person:user
以上命令成功运行结束后我们来检查下 HBase 和 Phoenix 两边的数据:HBase:
Phoenix:
上述的测试是为了说明,数据不同的写入方式对 HBase 和 Phoenix 两边的数据绑定是否有不同的影响,而目前看来,似乎没有什么特殊的地方。后续还会测试数据不同的写入方式,对 Phoenix 索引数据的影响,在那种场景下,还会是一样的结果吗?让我们拭目以待!
8. 创建多个列簇 Phoenix 映射表
这一小节补充下多列簇 Phoenix 映射表的创建,直接上建表语句:
create table "person"."user2"(
"id" VARCHAR PRIMARY KEY,
"info"."name" VARCHAR,
"info"."age" VARCHAR,
"info2"."name" VARCHAR,
"info2"."age" VARCHAR,
"info2"."address" VARCHAR)
column_encoded_bytes=;
以 put 的方式插入一些数据,然后在 Phoenix 中做以下查询:
对于所属不同列簇但名称相同的字段的查询,一定要显示指定字段所属的列簇名,不然查询时会报异常。除此之外,跟单列簇创建方式相比,倒也没啥特别之处了。
9. Phoenix 视图
类似 MySql 等关系型数据库中的视图,Phoenix 也有自己的视图概念,其视图的创建方式与表的创建语句类似。如果单纯只是为了查询数据,强烈建议为 HBase 表创建 Phenix 的视图映射,此时视图的删除对 HBase 的源表不会造成任何影响。
之前在倒腾 Phoenix 视图的时候,频繁遇到各种各样的报错,直至现在,对于其中的有些异常,依旧不明就里。下面先直接演示能正确创建视图的语句吧!
首先创建一张 HBase 表,create 'USER','INFO'
不要为这张表创建其 Phoenix 映射表,然后运行如下创建视图的语句。
create view USER(
ID VARCHAR PRIMARY KEY,
INFO.NAME VARCHAR,
INFO.AGE VARCHAR);
在 HBase 中插入一些数据,观察视图中的数据是否会实时更新。答案是,在 HBase 中插入数据,Phoenix 视图会实时更新数据。
那么以 bulkload 的方式更新数据呢?根据上文中提到的测试方法,我们再来实施一下。答案是,以 bulkload 的方式把数据导入 HBase 中,Phoenix 依旧会实时更新数据。
反过来,我们是否可以在 Phoenix 中 upset 数据呢?
upsert into USER (ID,NAME,AGE) values('111', 'leo111','17');
视图是只读的,不能做修改之类的操作。此时删掉视图,对 HBase 源表是没有影响的。
drop view USER;
scan 'USER'
再来说明一下当初折腾视图时遇到的各种各样的异常。
「HBase 源表不存在而直接创建视图」
看来 Phoenix 视图创建跟 Phoenix 表创建有很大不同啊。
「HBase 源表存在且 Phoenix 映射表也存在」
删除掉原来的测试表,然后直接用创建 Phoenix 表的方式,同时创建出 HBase 的源表。
create table USER(
ID VARCHAR PRIMARY KEY,
INFO.NAME VARCHAR,
INFO.AGE VARCHAR)
column_encoded_bytes=;
此时我们再来运行视图创建语句。
create view USER(
ID VARCHAR PRIMARY KEY,
INFO.NAME VARCHAR,
INFO.AGE VARCHAR);
如果直接运行这个语句,不出意外一定会报错,报错信息如下:Error: ERROR 1036 (42J04): Cannot modify the primary key of a VIEW if last PK column of parent is variable length. columnName=USER.ID (state=42J04,code=1036)
table 的名字是 USER,view 的名字也是 USER,但这真的是报错的根本原因吗?那让我们换一个名字来试试看。
create view USER_VIEW(
ID VARCHAR PRIMARY KEY,
INFO.NAME VARCHAR,
INFO.AGE VARCHAR);
继续报错:
Error: ERROR 505 (42000): Table is read only. (state=42000,code=505),又回到了我们刚开始的异常,HBase 源表不存在而直接创建视图。这时候如果你一顿谷歌加百度,十有八九会遇到类似这样的视图创建语句。
create view USER_VIEW(
ID VARCHAR PRIMARY KEY,
INFO.NAME VARCHAR,
INFO.AGE VARCHAR) as select * from USER;
指定查询数据集,也就是 select 的语句,再映射到视图 USER_VIEW 上,依旧报错:
Error: ERROR 1036 (42J04): Cannot modify the primary key of a VIEW if last PK column of parent is variable length. columnName=USER_VIEW.ID (state=42J04,code=1036)
聪明的你一定会这样修改上述的语句。
create view USER_VIEW as select * from USER;
这样的创建语句其实很类似 hive 还有其他的一些关系型数据库。如果,你还想创建指定列的视图呢?
create view USER_VIEW_NAME as select NAME from USER;
你以为这样写就可以了吗?脸黑的我又遇见了这样的报错。
Error: ERROR 604 (42P00): Syntax error. Mismatched input. Expecting "ASTERISK", got "NAME" at line 1, column 38. (state=42P00,code=604)
百度了一波,从大佬的博客里了解到,目前在 Phoenix 创建视图时,子句中不能选择字段,只能用*号,否则就会报如上错误。
是否可以创建语法更复杂的视图呢?例如,我已经有了一张 USER 表,再创建一张 PAY 表,记录每个用户的花销。然后以 JOIN 的方式创建视图 USER_PAY_VIEW。
当前版本的视图支持的语法比较有限,SELECT 中不能有复杂的语法,否则解析时会无法判断结束标记。
视图使用时,一定要注意,在没有创建索引的情况下,不要瞎写 SQL 语句,什么 group by,order by。否则一旦表里的数据量过大,你十有八九能遇到全表扫描,严重情况下影响线上业务。这样的规则,对表的查询同样适用。
10. 后
后来总结一下 Phoenix 的常规使用吧。Phoenix 表创建时要注意大小写,如果需要使用小写,那么请给表名,列簇名,字段名加上英文类型的双引号;如果你的 HBase 表数据量过大,创建表时,请尽可能提前把对应的 Phoenix 映射表也创建出来吧,后再导入数据;针对建表时的预分区操作,文中也提到了两种方式。如果对表仅有查询需要,那么请考虑创建视图,但视图的使用又有很大的限制,使用不当就会导致全表扫描,不仅影响查询效率,还会影响线上的业务。
相关文章