1. CREATE 语句能干嘛
CREATE 系列语句的目标只有一个:把对象注册到当前或指定 Catalog 中,让它们能被后续 SQL 直接引用(查询、写入、函数调用、模型推理等)。
当前 Flink SQL 常见支持:
CREATE TABLE[CREATE OR] REPLACE TABLECREATE CATALOGCREATE DATABASECREATE VIEWCREATE FUNCTIONCREATE MODEL
2. 如何执行 CREATE:Java TableEnvironment 一把梭
在 Java 里,CREATE 语句就是一条 SQL 字符串,直接丢给 TableEnvironment.executeSql():
java
TableEnvironment tableEnv = TableEnvironment.create(...);
// 1) 注册源表
tableEnv.executeSql(
"CREATE TABLE Orders (`user` BIGINT, product STRING, amount INT) WITH (...)"
);
// 2) 注册 sink 表
tableEnv.executeSql(
"CREATE TABLE RubberOrders(product STRING, amount INT) WITH (...)"
);
// 3) 查询
Table result = tableEnv.sqlQuery(
"SELECT product, amount FROM Orders WHERE product LIKE '%Rubber%'"
);
// 4) 写入
tableEnv.executeSql(
"INSERT INTO RubberOrders " +
"SELECT product, amount FROM Orders WHERE product LIKE '%Rubber%'"
);
SQL Client 场景通常更简单:直接在 CLI 里执行 CREATE 即可(依赖通常已带齐)。
3. CREATE TABLE:Flink 建表的"核心玩法"
3.1 总体语法骨架(你可以把它当模板)
你在生产里 90% 的建表,都可以从这个骨架改出来:
sql
CREATE TABLE [IF NOT EXISTS] [catalog.][db.]table_name (
-- 1) 物理列 / 元数据列 / 计算列(三选一或混合)
...
-- 2) watermark(可选,但事件时间流基本必备)
[ WATERMARK FOR rowtime AS ... ]
-- 3) 主键/约束(可选,但 upsert/更新流强烈推荐)
[ PRIMARY KEY (...) NOT ENFORCED ]
)
[COMMENT '...']
[ DISTRIBUTED BY ... | DISTRIBUTED INTO ... ] -- 桶/分布(可选)
[ PARTITIONED BY (...) ] -- 分区(可选,filesystem 常用)
WITH (
'connector'='...',
...
)
[ LIKE source_table (...) | AS select_query ];
4. 列的三种形态:物理列 / 元数据列 / 计算列
4.1 物理列(Physical Columns)
最普通的列:定义字段名、类型、顺序。connector/format 读取与写出基本都依赖物理列。
sql
CREATE TABLE MyTable (
user_id BIGINT,
name STRING
) WITH (...);
4.2 元数据列(Metadata Columns)
用于把 connector/format 的"行级元信息"显式暴露出来(比如 Kafka 的 timestamp、offset 等)。
sql
CREATE TABLE MyTable (
user_id BIGINT,
name STRING,
record_time TIMESTAMP_LTZ(3) METADATA FROM 'timestamp'
) WITH (
'connector'='kafka',
...
);
常见要点:
FROM 'xxx'可以省略:列名就当 metadata key- 类型允许兼容 cast(比如把 timestamp 映射成 BIGINT)
VIRTUAL:只参与读(source-to-query),不参与写出(query-to-sink)
sql
CREATE TABLE MyTable (
`timestamp` BIGINT METADATA,
`offset` BIGINT METADATA VIRTUAL,
user_id BIGINT,
name STRING
) WITH ('connector'='kafka', ...);
4.3 计算列(Computed Columns)
用表达式生成的"虚拟列",不会物理落盘/落 Kafka。非常常用来:
- 补齐处理时间:
proctime AS PROCTIME() - 从字符串/JSON 转换事件时间
- 做派生指标:
cost AS price * quantity
sql
CREATE TABLE MyTable (
user_id BIGINT,
price DOUBLE,
quantity DOUBLE,
cost AS price * quantity
) WITH (...);
计算列不能作为 INSERT INTO 的目标列(因为不持久化)。
5. WATERMARK:事件时间语义的"开关"
水位线声明形式:
sql
WATERMARK FOR rowtime_column AS watermark_expression
关键规则(记住这三条,基本不踩坑):
rowtime_column必须是表中已存在的列- 事件时间列通常要求是
TIMESTAMP(3)(可来自计算列) - watermark 表达式返回值也必须是
TIMESTAMP(3)
常用策略:
5.1 严格递增(几乎不允许乱序)
sql
WATERMARK FOR rowtime AS rowtime
5.2 允许 5 秒乱序(生产最常见)
sql
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
完整例子:
sql
CREATE TABLE Orders (
`user` BIGINT,
product STRING,
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (...);
6. PRIMARY KEY:不是约束检查,而是"优化提示"
Flink 的主键语义要记住一句话:
Flink 只支持
NOT ENFORCED,不会帮你检查数据是否真的唯一;你要自己保证。
但主键仍然非常重要,因为它会影响:
- Planner 的优化(例如 upsert、changelog 推导)
- 下游 sink 的写入语义(比如 upsert-kafka/jdbc upsert)
写法两种:
6.1 列级主键
sql
id BIGINT PRIMARY KEY NOT ENFORCED
6.2 表级主键
sql
PRIMARY KEY (id, biz_date) NOT ENFORCED
注意:主键列不可为空;Flink 会据此调整列的 nullability 假设。
7. PARTITIONED BY 与 DISTRIBUTED:分区 vs 桶(别搞混)
7.1 PARTITIONED BY:典型用于 filesystem sink
按分区列生成目录层级(Hive/Filesystem 场景最常见):
sql
PARTITIONED BY (dt, region)
7.2 DISTRIBUTED / BUCKET:典型用于负载均衡与并行写
桶(buckets)把"可能无限的 keyspace"切成更可管理的小片,利于并行处理与下游存储的写放大控制。
常见写法:
sql
-- 指定 HASH + 桶数
CREATE TABLE MyTable (uid BIGINT, name STRING)
DISTRIBUTED BY HASH(uid) INTO 4 BUCKETS;
-- 让 connector 自选算法,只给桶键与桶数
CREATE TABLE MyTable (uid BIGINT, name STRING)
DISTRIBUTED BY (uid) INTO 4 BUCKETS;
-- 只给桶键,桶数交给 connector
CREATE TABLE MyTable (uid BIGINT, name STRING)
DISTRIBUTED BY (uid);
-- 只给桶数
CREATE TABLE MyTable (uid BIGINT, name STRING)
DISTRIBUTED INTO 4 BUCKETS;
8. WITH Options:连接器与格式的"入口"
WITH ('k1'='v1', 'k2'='v2') 用来描述 connector/format 等属性。要点:
- key/value 都必须是字符串字面量
- 表一旦注册,既可以作为 source 也可以作为 sink(直到被 DML 引用时才确定角色)
- 表名可以是:
catalog.db.table/db.table/table
9. LIKE:复用表定义,做"继承 + 覆盖"
LIKE 可以基于已有表复用定义,并选择:
INCLUDING / EXCLUDING哪些部分(约束、分布、分区、watermark、options、generated、metadata...)- 是否
OVERWRITING(遇到冲突 key 时用新表覆盖)
典型场景:复用 schema,把 connector 从 filesystem 换成 kafka,同时补 watermark。
sql
CREATE TABLE Orders (
`user` BIGINT,
product STRING,
order_time TIMESTAMP(3)
) WITH (
'connector'='kafka',
'scan.startup.mode'='earliest-offset'
);
CREATE TABLE Orders_with_watermark (
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (
'scan.startup.mode'='latest-offset'
)
LIKE Orders;
10. CTAS:Create Table As Select(建表 + 插入一条命令完成)
CTAS 适合"快速从查询结果生成一张新表":
sql
CREATE TABLE my_ctas_table
WITH ('connector'='kafka', ...)
AS
SELECT id, name, age
FROM source_table
WHERE MOD(id, 10) = 0;
你也可以在 CREATE 部分显式加:
- computed columns
- watermark
- primary key
- distributed
但注意两类限制(很容易踩):
- 暂不支持创建 temporary table
- 暂不支持创建 partitioned table
- 默认 非原子:插入失败不会自动回滚删除已创建的表
10.1 想要"原子 CTAS"怎么办?
两件事同时满足:
- sink 侧实现了 CTAS 原子语义(通常依赖 staging 能力)
- 设置:
sql
SET 'table.rtas-ctas.atomicity-enabled' = 'true';
11. RTAS / REPLACE TABLE:一条语句"替换并回填"
REPLACE TABLE AS SELECT / CREATE OR REPLACE TABLE AS SELECT 用于:
- 表存在:直接替换(语义上像 drop + create + insert)
CREATE OR REPLACE:不存在就创建,存在就替换
sql
REPLACE TABLE my_rtas_table
WITH ('connector'='kafka', ...)
AS
SELECT id, name, age
FROM source_table
WHERE MOD(id, 10) = 0;
RTAS 同样默认 非原子,并且语义上先 drop 再建再写;如果在 in-memory catalog 下,drop 只是从 catalog 移除,不一定删除物理数据(这点生产上要特别注意)。
想要原子 RTAS:同 CTAS,依赖 sink 支持 +
table.rtas-ctas.atomicity-enabled=true。
12. 其它 CREATE:Catalog / Database / View / Function / Model
12.1 CREATE CATALOG
sql
CREATE CATALOG [IF NOT EXISTS] my_catalog
WITH ('type'='...', ...);
12.2 CREATE DATABASE
sql
CREATE DATABASE [IF NOT EXISTS] my_db
WITH ('k'='v', ...);
12.3 CREATE VIEW(支持 TEMPORARY)
sql
CREATE [TEMPORARY] VIEW [IF NOT EXISTS] my_view AS
SELECT ...
12.4 CREATE FUNCTION(JAVA/SCALA/PYTHON)
支持临时函数与系统临时函数:
sql
CREATE TEMPORARY FUNCTION my_udf
AS 'com.xxx.MyUdf'
LANGUAGE JAVA;
USING JAR 当前只支持 JAVA/SCALA(把实现与依赖 jar 引入)。
12.5 CREATE MODEL(把"模型推理"注册成对象)
sql
CREATE MODEL sentiment_analysis_model
INPUT (text STRING)
OUTPUT (sentiment STRING)
WITH (
'provider' = 'openai',
'endpoint' = 'https://api.openai.com/v1/chat/completions',
'api-key' = '<YOUR KEY>',
'model' = 'gpt-3.5-turbo',
'system-prompt' = '...'
);
13. 最佳实践速查(建议你写进博客结尾)
- Kafka/Upsert/JDBC 这类更新流:优先声明
PRIMARY KEY ... NOT ENFORCED - 事件时间作业:一定写
WATERMARK(并用合理乱序区间) - 元数据列:offset 这类"只读"字段用
VIRTUAL,避免写出 schema 不一致 - 用 LIKE 做"复用定义 + 覆盖 options",比复制粘贴更稳
- CTAS/RTAS 上生产前先确认:是否需要原子性?sink 是否支持 staging?