>
> 注意:此时表名需要使用`user2`,而不是`paimon_external_user2`!!!
>
>
>
因为`paimon_external_user2`这个表名是存储在Flink SQL中`默认的基于内存的catalog`中,当任务执行结束之后,`paimon_external_user2`这个表名就不存在了。
最终`Paimon`中存储的表名是`user2`,这个表名来源于path路径中的名称。
执行修改后的代码`FlinkSQLReadFromPaimon`,可以看到如下结果:
±—±-------------------------------±------------+
| op | name | age |
±—±-------------------------------±------------+
| +I | jack | 18 |
注意:此时我们是以内部表的形式读取的数据。
#### 3.1.3 分区表
Paimon中的分区表和Hive中的分区表的功能类似,主要也是为了提高查询效率。
分区表中的分区字段可以支持`1个`或者`多个`。
>
> 注意:如果表中定义了主键,分区字段则必须是主键的子集。
>
>
>
Paimon分区表的建表语句是这样的:
CREATE TABLE Partition_Table (
id INT,
name STRING,
dt STRING,
hh STRING,
PRIMARY KEY (id, dt, hh) NOT ENFORCED
) PARTITIONED BY (dt, hh) WITH(
…
)
核心语法是`PARTITIONED BY (....)`
下面我们来通过具体的案例来演示一下Paimon分区表的使用。
首先来看一下如何在Flink SQL中向Paimon分区表中写入数据。
创建object:`FlinkSQLWritePaimonPartitionTable`
代码如下:
package tech.xuwei.paimon.tabletype
import org.apache.flink.api.common.RuntimeExecutionMode
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment
/**
-
使用FlinkSQL向Paimon分区表中写入数据
-
Created by xuwei
*/
object FlinkSQLWritePaimonPartitionTable {
def main(args: Array[String]): Unit = {
//获取执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setRuntimeMode(RuntimeExecutionMode.STREAMING)
val tEnv = StreamTableEnvironment.create(env)//创建Paimon类型的Catalog
tEnv.executeSql(
“”"
|CREATE CATALOG paimon_catalog WITH (
| ‘type’=‘paimon’,
| ‘warehouse’=‘hdfs://bigdata01:9000/paimon’
|)
|“”".stripMargin)
tEnv.executeSql(“USE CATALOG paimon_catalog”)//创建Paimon分区表
tEnv.executeSql(
“”"
|CREATE TABLE IF NOT EXISTS user_par (
| id INT,
| name STRING,
| dt STRING,
| hh STRING,
| PRIMARY KEY (id, dt, hh) NOT ENFORCED
|) PARTITIONED BY (dt, hh)
|“”".stripMargin)//向Paimon分区表中写入数据
tEnv.executeSql(
“”"
|INSERT INTO user_par(id,name,dt,hh)
|VALUES (1,‘jack’,‘20230101’,‘10’),(2,‘tom’,‘20230101’,‘11’)
|“”".stripMargin)
}
}
执行代码。
此时到hdfs中可以看到这个表的底层数据是按照分区字段作为目录名称来存储的。
[root@bigdata04 ~]# hdfs dfs -ls /paimon/default.db/user_par/dt=20230101
Found 4 items
drwxr-xr-x - yehua supergroup 0 2028-11-28 17:08 /paimon/default.db/user_par/dt=20230101/hh=10
drwxr-xr-x - yehua supergroup 0 2028-11-28 17:08 /paimon/default.db/user_par/dt=20230101/hh=11
接下来我们来看一下如何在Flink SQL中从Paimon分区表中读取数据。
其实读取数据这一块是没什么特殊之处的,不管是内部表、外部表还是分区表。
只是针对分区表,如果想要提高查询效率,则需要在where子句中指定分区字段。
创建object:`FlinkSQLReadPaimonPartitionTable`
代码如下:
package tech.xuwei.paimon.tabletype
import org.apache.flink.api.common.RuntimeExecutionMode
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment
/**
-
使用FlinkSQL从Paimon分区表中读取数据
-
Created by xuwei
*/
object FlinkSQLReadPaimonPartitionTable {
def main(args: Array[String]): Unit = {
//获取执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setRuntimeMode(RuntimeExecutionMode.STREAMING)
val tEnv = StreamTableEnvironment.create(env)//创建Paimon类型的Catalog
tEnv.executeSql(
“”"
|CREATE CATALOG paimon_catalog WITH (
| ‘type’=‘paimon’,
| ‘warehouse’=‘hdfs://bigdata01:9000/paimon’
|)
|“”".stripMargin)
tEnv.executeSql(“USE CATALOG paimon_catalog”)//执行查询,并且打印输出结果
tEnv.executeSql(
“”"
|SELECT
| *
|FROMpaimon_catalog
.default
.user_par
|WHERE dt = ‘20230101’ AND hh in (‘10’,‘11’)
|“”".stripMargin)
.print()
}
}
执行代码,可以看到如下结果:
±—±------------±-------------------------------±-------------------------------±-------------------------------+
| op | id | name | dt | hh |
±—±------------±-------------------------------±-------------------------------±-------------------------------+
| +I | 1 | jack | 20230101 | 10 |
| +I | 2 | tom | 20230101 | 11 |
这就是Paimon分区表的使用了。
#### 3.1.4 临时表
最后我们来看一下临时表,前面我们已经用过临时表了。
临时表是由`Paimon Catalog`临时进行记录,但是不由它管理。删除临时表时也不会删除表中的数据文件。
也就是说这个临时表的元数据信息只会临时存储在`Paimon Catalog`里面,当Flink SQL任务执行结束之后,这个临时表的元数据信息就会被删除了。
>
> 注意:其实这个临时表的特性是Flink SQL提供的,所以目前只能在Flink SQL中使用临时表。
>
>
>
临时表的典型应用场景是这样的,就是我们想要在`Paimon Catalog`里面使用`其他类型`的表。
因为在Paimon Catalog里面定义表的时候是不允许指定connector属性的,所以说如果我们想要通过connector指定kafka或者其他类型的数据存储系统,就需要定义临时表了。
下面我们来看一个例子,加深我们对临时表应用场景的理解:
首先我们创建了Paimon Catalog,并且进入了这个Catalog里面。
CREATE CATALOG paimon_catalog WITH (
‘type’ = ‘paimon’,
‘warehouse’ = ‘hdfs:///path/to/warehouse’
);
USE CATALOG paimon_catalog;
然后我们在Paimon Catalog里面创建了两个表:`t1`和`t2`。
CREATE TABLE t1(…) WITH (…);
CREATE TEMPORARY TABLE t2(
id INT,
name STRING
) WITH (
‘connector’ = ‘filesystem’,
‘path’ = ‘hdfs://…/data.json’,
‘format’ = ‘json’
);
注意:t1是Paimon类型的表。t2是临时表。
t2表中的数据来源于hdfs中的json数据文件,所以此时我们在Paimon Catalog里面创建t2表的时候就需要使用临时表了,因为我们需要通过connector指定数据的存储位置为filesystem。
如果不使用临时表,在Paimon Catalog里面创建t2的时候就不能使用connector属性了。
最后就可以在Paimon Catalog里面直接操作这两个表了。
SELECT t1.id,t2.name,t1.age FROM t1 JOIN t2 ON t1.id = t2.id;
如果我不想使用临时表,但是还想实现这个需求,应该怎么做呢?
其实也很简单,我们只需要在默认的`default catalog`里面创建表`t2`即可。
但是需要注意,后续我们在同时使用表`t1`和`t2`的时候,就需要指定表的完整名称了。
类似于`FlinkSQLWriteToPaimon`这个案例。
//向目的地表中写入数据
tEnv.executeSql(
“”"
|INSERT INTO paimon_catalog
.default
.wc_sink_sql
|SELECT
| word,
| COUNT(*) as cnt
|FROM default_catalog
.default_database
.word_source
|GROUP BY word
|“”".stripMargin).print()
* 如果是在paimon catalog里面执行这个SQL,则需要给`word_source`这个表指定完整名称。
* 如果是在的default catalog里面执行这个SQL,则需要给`wc_sink_sql`这个表指定完整名称。
这就是临时表的典型应用场景。
### 3.2 存储维度
从存储维度来看,Paimon中的表可以分为两种:
* `Primary Key`表,也可以称之为主键表。
* `Append Only`表,也可以称之为仅追加表。
这两种表其实很好区分,如果表中定义的有主键字段,则是主键表;如果表中没有定义主键字段,则是仅追加表。
下面我们来详细分析一下。
#### 3.2.1 Primary Key表(主键表)
主键表中包含主键字段,可以支持新增、更新和删除表中的数据。
>
> 注意:主键可以由一个或者多个字段组成。
>
>
>
主键表其实我们前面已经使用过了,就是在建表语句中通过`PRIMARY KEY`来指定主键字段。
主键表中还包含了多个高级特性:
* Bucket
* Changelog Producers
* Merge Engines
* Sequence Field
下面我们来具体看一下这些高级特性。
##### 3.2.1.1 Bucket
Bucket:可以翻译为桶。
Bucket是表中数据读写的最小存储单元,所以Bucket的数量限制了读写的并行度,最终会影响读写性能,每个Bucket目录内部会包含一棵LSM树。
>
> 注意:LSM树是一种数据结构,Paimon采用了LSM树作为其文件存储的数据结构。
>
>
>
主键表目前支持两种`Bucket mode`(模式):
* 1:`Fixed Bucket mode`:属于固定Bucket数量模式,也就是需要我们手工指定Bucket的数量。我们在建表时默认使用的就是这种模式,Bucket参数的值默认为1。我们只需要给Bucket设置一个大于0的数值即可。但是需要注意:Bucket数量过大会导致小文件过多,影响读取性能;Bucket数量过小会影响写入性能。一般情况下,每个Bucket中的数据量推荐为1G左右。
* 2:`Dynamic Bucket mode`:属于动态Bucket数量模式,也就是说Bucket的数量是动态变化的。此时我们需要在建表时指定`'bucket' = '-1'`,此时会由Paimon动态维护索引,将每个Bucket中的数据条数控制在2000000(2百万)以下,这个数值是由`dynamic-bucket.target-row-num`这个参数控制的。但是需要注意,目前这种模式属于实验性质,暂时不建议在生产环境下使用。
下面我们通过一个案例来具体感受一下Bucket。
创建package:`tech.xuwei.paimon.bucket`
创建object:`BucketDemo`
代码如下:
package tech.xuwei.paimon.bucket
import org.apache.flink.api.common.RuntimeExecutionMode
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.table.api.bridge.scala.StreamTableEnvironment
/**
-
验证Bucket特性
-
Created by xuwei
*/
object BucketDemo {
def main(args: Array[String]): Unit = {
//创建执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setRuntimeMode(RuntimeExecutionMode.STREAMING)
val tEnv = StreamTableEnvironment.create(env)//创建Paimon类型的Catalog
tEnv.executeSql(
“”"
|CREATE CATALOG paimon_catalog WITH (
| ‘type’=‘paimon’,
| ‘warehouse’=‘hdfs://bigdata01:9000/paimon’
|)
|“”".stripMargin)
tEnv.executeSql(“USE CATALOG paimon_catalog”)//创建Paimon表
tEnv.executeSql(
“”"
|CREATE TABLE IF NOT EXISTS bucket_test (
| word STRING,
| cnt BIGINT,
| PRIMARY KEY (word) NOT ENFORCED
|)WITH(
| ‘bucket’ = ‘2’ – 手工指定bucket的值,默认为1
|)
|“”".stripMargin)//查看最完整的建表语句
tEnv.executeSql(“SHOW CREATE TABLE bucket_test”).print()//向表中添加数据
tEnv.executeSql(
“”"
|INSERT INTO bucket_test(word,cnt)
|VALUES(‘a’,1) , (‘b’,2) , (‘c’,1) , (‘d’,3)
|“”".stripMargin)
}
}
执行代码,可以看到输出的完整建表语句信息:
CREATE TABLE paimon_catalog
.default
.bucket_test
(
word
VARCHAR(2147483647) NOT NULL,
cnt
BIGINT,
CONSTRAINT 53dc473d-3e56-4e13-ac8b-a4f3c9abb72b
PRIMARY KEY (word
) NOT ENFORCED
) WITH (
‘bucket’ = ‘2’,
‘path’ = ‘hdfs://bigdata01:9000/paimon/default.db/bucket_test’
)
接下来我们到hdfs中查看一个这个表的Bucket信息:
[root@bigdata04 ~]# hdfs dfs -ls /paimon/default.db/bucket_test
Found 5 items
drwxr-xr-x - yehua supergroup 0 2028-11-29 17:54 /paimon/default.db/bucket_test/bucket-0
drwxr-xr-x - yehua supergroup 0 2028-11-29 17:54 /paimon/default.db/bucket_test/bucket-1
drwxr-xr-x - yehua supergroup 0 2028-11-29 17:54 /paimon/default.db/bucket_test/manifest
drwxr-xr-x - yehua supergroup 0 2028-11-29 17:54 /paimon/default.db/bucket_test/schema
drwxr-xr-x - yehua supergroup 0 2028-11-29 17:54 /paimon/default.db/bucket_test/snapshot
此时可以看到,这个表中包含2个bucket目录,这两个bucket目录中存储的就是这个表中的所有数据。
bucket目录内部都是一些data数据文件,里面就是真实的数据内容了:
[root@bigdata04 ~]# hdfs dfs -ls /paimon/default.db/bucket_test/bucket-0
Found 1 items
-rw-r–r-- 3 yehua supergroup 545 2028-11-29 17:54 /paimon/default.db/bucket_test/bucket-0/data-8475e141-1725-489f-bd09-13606fd2302f-0.orc
咱们之前创建的表没有手工指定bucket,那么bucket默认为1,可以到表wc\_sink\_sql里面看一下,这个表里面存储了多条数据,但是只有1个bucket:
[root@bigdata04 ~]# hdfs dfs -ls /paimon/default.db/wc_sink_sql
Found 4 items
drwxr-xr-x - yehua supergroup 0 2028-11-29 11:53 /paimon/default.db/wc_sink_sql/bucket-0
drwxr-xr-x - yehua supergroup 0 2028-11-29 11:53 /paimon/default.db/wc_sink_sql/manifest
drwxr-xr-x - yehua supergroup 0 2028-11-29 11:44 /paim