Flink SQL 语言从 DDL 到 DML,再到关键字与数据类型

官方的一句话概括:

Flink SQL 基于 Apache Calcite,遵循 SQL 标准做了扩展,是 Flink Table & SQL 生态的核心语言。

几个要点:

  • 语义上尽可能兼容标准 SQL,大多数 DDL/DML/查询语法都很熟悉;
  • 底层解析和优化依赖 Apache Calcite,因此很多保留关键字、语法规则来自 Calcite/SQL 标准;
  • 结合 Flink 自己的特性(流式表、动态表、时间属性、水位线等),做了大量扩展。

Flink 目前支持的主要 SQL 语句包括(按类别 roughly 分组):

  • 查询语言(Query)SELECT
  • DDLCREATE / DROP / ALTER / ANALYZE
  • DMLINSERT / UPDATE / DELETE
  • 元信息与调试DESCRIBE / EXPLAIN / SHOW / USE / LOAD / UNLOAD

接下来按功能逐块拆开。

2. 查询语言:SELECT(Queries)

Flink 的查询语言入口依然是最经典的 SELECT

sql 复制代码
SELECT
  user_id,
  COUNT(*) AS cnt
FROM user_events
WHERE event_type = 'click'
GROUP BY user_id;

在 Flink 里,所有 DML 的起点基本都是一个 SELECT,只不过它的目的地可能是:

  • 直接在 SQL Client 里打印;
  • 或者通过 INSERT INTO target_table 写到 Kafka / MySQL / Iceberg 等外部系统。

流批一体的特点在 SQL 层也成立:

  • 批模式SELECT 对静态数据做一次性查询;
  • 流模式SELECT 对动态表(持续变化的表)持续计算,生成一个 changelog 流。

3. DDL:CREATE / DROP / ALTER / ANALYZE 等

3.1 CREATE:建表、建库、建视图、建函数

Flink SQL 的 CREATE 家族包括:

  • CREATE TABLE
  • CREATE CATALOG
  • CREATE DATABASE
  • CREATE VIEW
  • CREATE FUNCTION
3.1.1 CREATE TABLE:最常用的 DDL

典型示例(Kafka 源表):

sql 复制代码
CREATE TABLE user_events (
  user_id      STRING,
  event_type   STRING,
  ts           BIGINT,
  row_time     AS TO_TIMESTAMP_LTZ(ts, 3),
  WATERMARK FOR row_time AS row_time - INTERVAL '5' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'user_events',
  'properties.bootstrap.servers' = 'kafka:9092',
  'format' = 'json'
);

要点:

  • 字段定义 + 计算列(AS ...)+ WATERMARK(事件时间属性);
  • WITH 里配置 connector、topic、格式等;
  • 这类建表是 动态表(Dynamic Table) 的入口,后续 SQL 都基于它。
3.1.2 CREATE VIEW:保存查询结果的逻辑定义

视图不会真正存储数据,它只是一个 查询的别名

sql 复制代码
CREATE VIEW active_users_10m AS
SELECT
  TUMBLE_START(row_time, INTERVAL '10' MINUTE) AS win_start,
  user_id,
  COUNT(*) AS cnt
FROM user_events
GROUP BY
  TUMBLE(row_time, INTERVAL '10' MINUTE),
  user_id;

后续你可以直接:

sql 复制代码
SELECT * FROM active_users_10m WHERE cnt > 100;
3.1.3 CREATE FUNCTION:注册 UDF/UDAF/UDTF

比如把 Java 里写好的 UDF 注册成 SQL 函数:

sql 复制代码
CREATE FUNCTION normalize_name AS 'com.example.udf.NormalizeName';

然后:

sql 复制代码
SELECT normalize_name(user_name) FROM users;

3.2 DROP:删表、删库、删视图、删函数

  • DROP TABLE
  • DROP DATABASE
  • DROP VIEW
  • DROP FUNCTION

例子:

sql 复制代码
DROP TABLE IF EXISTS tmp_user_events;
DROP VIEW active_users_10m;
DROP FUNCTION normalize_name;

注意和外部存储的绑定关系:

Flink 的 DROP TABLE 通常只删掉 catalog 中的元信息,是否真正删掉底层存储(如 Kafka topic / HDFS 目录),要看具体 connector/catal og 的实现。

3.3 ALTER:修改表、库、函数属性

Flink 支持的 ALTER 能力相对有限,大致包括:

  • 修改表的属性(如 connector 选项);
  • (部分版本)支持列的增删改、主键标记调整;
  • 修改函数类型(TEMPORARY 与否等);
  • 修改数据库属性。

示意:

sql 复制代码
ALTER TABLE user_events SET (
  'scan.startup.mode' = 'latest-offset'
);

3.4 ANALYZE TABLE:统计表的统计信息(接近优化器提示)

ANALYZE TABLE 会收集表的行数、列的基数等统计信息,帮助优化器做更好的 Join 顺序、执行计划选择:

sql 复制代码
ANALYZE TABLE user_events COMPUTE STATISTICS;

在大规模复杂查询(尤其是 Batch 或 Hybrid)的场景里,这个会对计划质量有帮助。

4. DML:INSERT / UPDATE / DELETE

4.1 INSERT:最常用的写入语句

Flink SQL 中最常见的生产语句就是:

sql 复制代码
INSERT INTO sink_table
SELECT ... FROM source_table ...;

例子:

sql 复制代码
INSERT INTO big_screen_metrics
SELECT
  TUMBLE_START(row_time, INTERVAL '1' MINUTE) AS window_start,
  app_id,
  COUNT(*) AS pv,
  COUNT(DISTINCT user_id) AS uv
FROM user_events
GROUP BY
  TUMBLE(row_time, INTERVAL '1' MINUTE),
  app_id;

在 SQL Client 或应用中,这个 INSERT 一旦提交:

  • 流模式:就会变成一个持续运行的流任务;
  • 批模式:就是一条离线任务,跑完即止。

4.2 UPDATE / DELETE:基于主键的更新与删除

在 Flink SQL 里,UPDATE / DELETE 通常依赖:

  • 表上存在主键(PRIMARY KEY NOT ENFORCED)
  • 底层 connector 支持 upsert / delete 语义(如 Upsert-Kafka、一些数据库 sink)。

简单例子(伪示意,实际支持要看版本和 connector):

sql 复制代码
UPDATE user_status
SET state = 'disabled'
WHERE user_id = 'u_001';

DELETE FROM user_status
WHERE user_id = 'u_002';

在流式语义下,这些会被转成 changelog(UPDATE_BEFORE / UPDATE_AFTER / DELETE)写入 sink。

5. 元信息与运维相关语句:DESCRIBE / EXPLAIN / USE / SHOW / LOAD / UNLOAD

5.1 DESCRIBE:查看表结构与时间属性

sql 复制代码
DESCRIBE user_events;

输出中包括:

  • 字段名、类型、是否 nullable;
  • 是否是 *ROWTIME* / *PROCTIME*
  • Wartermark 相关信息。

在调 flink-sql 或排查时间属性/水位线问题时非常好用。

5.2 EXPLAIN:查看执行计划

sql 复制代码
EXPLAIN
SELECT
  user_id,
  COUNT(*) AS cnt
FROM user_events
GROUP BY user_id;

会展示:

  • 逻辑计划;
  • 优化后的物理计划;
  • 各种算子链路、shuffle、并行度信息(视配置和版本而定)。

调性能时必看。

5.3 USE:切换 catalog / database

类似于 MySQL 里的 USE db;

sql 复制代码
USE CATALOG my_catalog;
USE my_database;

5.4 SHOW:列出资源

常见用法:

sql 复制代码
SHOW CATALOGS;
SHOW DATABASES;
SHOW TABLES;
SHOW FUNCTIONS;

在交互式地探索环境时非常好用。

5.5 LOAD / UNLOAD

  • LOAD / UNLOAD 用于加载/卸载一些资源,会因 catalog/方言不同而略有差异;
  • 通常在需要动态加载 jar/模块、或存算分离场景下使用。

在日常业务开发中,LOAD/UNLOAD 出场频率相对较低。

6. SQL 中的数据类型:与 Data Types 文档的关系

在 DDL 语句里,Flink SQL 支持完整的数据类型体系(前文我们已经专门写过一篇)。

简单回顾几个要点:

  • 标准标量类型:BOOLEAN / TINYINT / SMALLINT / INT / BIGINT / FLOAT / DOUBLE / DECIMAL(p, s) 等;
  • 字符类型:CHAR(n) / VARCHAR(n) / STRING
  • 二进制:BINARY(n) / VARBINARY(n) / BYTES
  • 日期时间:DATE / TIME(p) / TIMESTAMP(p) / TIMESTAMP_LTZ(p) / 各种 INTERVAL
  • 复合类型:ARRAY<T> / MAP<K, V> / MULTISET<T> / ROW<...>
  • 特殊类型:RAW / VARIANT / STRUCTURED 等。

6.1 注意:有些类型在「查询表达式」里还不能完全使用

文档中特别提醒:

有些数据类型在 SQL 查询里(比如 cast 表达式、字面量)支持不完整。

例如在某些版本中,以下类型可能在表达式里受限制:

  • STRING
  • BYTES
  • RAW
  • TIME(p) WITHOUT TIME ZONE
  • TIME(p) WITH LOCAL TIME ZONE
  • TIMESTAMP(p) WITHOUT TIME ZONE
  • TIMESTAMP(p) WITH LOCAL TIME ZONE
  • ARRAY
  • MULTISET
  • ROW

这意味着:

  • 在 DDL 定义里你可以用这些类型;
  • 但在 SELECT 的表达式里,有的类型暂时不能直接写字面量,或者 CAST 不支持所有目标类型。

实战建议:

  • DDL 尽量用 Flink Data Types 文档中推荐的类型
  • 遇到 cast 报错时,优先简化为基础类型或用 UDF 做一些自定义转换。

7. 保留关键字(Reserved Keywords):为啥会突然报语法错?

文档最后给了一大长串保留关键字,比如:

SELECT, GROUP, ORDER, WINDOW, VIEW, TABLE, STREAM, OFFSET, VALUE, COUNT, MAP, RAW, STRING, INTERVAL, TIMESTAMP, TOP, YEAR, MONTH ......(共几百个)

这些关键字来自:

  • SQL 标准;
  • Calcite;
  • Flink 自己的扩展(如 STREAMSTRING 等)。

7.1 问题:我在建表时用了关键字当字段名

非常常见的坑,例如:

sql 复制代码
CREATE TABLE logs (
  timestamp TIMESTAMP(3),
  offset BIGINT,
  value STRING
) WITH (...);

结果建表就报语法错,或者后续 SELECT 时报错。

7.2 解决办法:用反引号 ````` 转义

文档明确说:

如果你想把一个保留关键字当作字段名使用,需要用反引号包起来,例如:
value, count, group

改写建表:

sql 复制代码
CREATE TABLE logs (
  `timestamp` TIMESTAMP(3),
  `offset` BIGINT,
  `value` STRING
) WITH (...);

查询时也一样:

sql 复制代码
SELECT `timestamp`, `offset`, `value` FROM logs;

7.3 实战命名建议

为了少踩坑,建议:

  • 字段名尽量 避免 与关键字重名,如 user, group, value, offset, timestamp, order 等;

  • 如果是从已有 MySQL/OLTP 库同步过来的 schema,可以:

    • 在 Flink 层用 不同名字 (如 ts 替代 timestamp);
    • 或者固定习惯:所有列名都不用关键字(新设计的库尽量遵守)。

8. 一个小型综合示例:从 DDL 到 Query 再到 Insert

假设我们要做一个简单的"用户行为 → 实时指标"的任务,可以用 Flink SQL 写成这样:

8.1 1)创建源表(Kafka)

sql 复制代码
CREATE TABLE user_events (
  user_id    STRING,
  event_type STRING,
  ts         BIGINT,
  row_time   AS TO_TIMESTAMP_LTZ(ts, 3),
  WATERMARK FOR row_time AS row_time - INTERVAL '5' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'user_events',
  'properties.bootstrap.servers' = 'kafka:9092',
  'format' = 'json'
);

8.2 2)创建结果表(还是 Kafka)

sql 复制代码
CREATE TABLE user_metrics_1m (
  window_start TIMESTAMP(3),
  window_end   TIMESTAMP(3),
  user_id      STRING,
  pv           BIGINT,
  uv           BIGINT
) WITH (
  'connector' = 'kafka',
  'topic' = 'user_metrics_1m',
  'properties.bootstrap.servers' = 'kafka:9092',
  'format' = 'json'
);

8.3 3)分析表结构,检查时间属性是否正确

sql 复制代码
DESCRIBE user_events;

看一下 row_time 是否被标记为 *ROWTIME*,watermark 是否生效。

8.4 4)先用 SELECT 做交互式调试

sql 复制代码
SELECT
  TUMBLE_START(row_time, INTERVAL '1' MINUTE) AS window_start,
  TUMBLE_END(row_time, INTERVAL '1' MINUTE)   AS window_end,
  user_id,
  COUNT(*) AS pv,
  COUNT(DISTINCT session_id) AS uv  -- 假设有 session_id
FROM user_events
WHERE event_type = 'page_view'
GROUP BY
  TUMBLE(row_time, INTERVAL '1' MINUTE),
  user_id;

8.5 5)用 EXPLAIN 看一下执行计划

sql 复制代码
EXPLAIN
INSERT INTO user_metrics_1m
SELECT
  TUMBLE_START(row_time, INTERVAL '1' MINUTE) AS window_start,
  TUMBLE_END(row_time, INTERVAL '1' MINUTE)   AS window_end,
  user_id,
  COUNT(*) AS pv,
  COUNT(DISTINCT session_id) AS uv
FROM user_events
WHERE event_type = 'page_view'
GROUP BY
  TUMBLE(row_time, INTERVAL '1' MINUTE),
  user_id;

确认:

  • 窗口算子;
  • groupBy key;
  • 是否有 shuffle;
  • 是否有不必要的 exchange。

8.6 6)正式提交 INSERT,启动任务

sql 复制代码
INSERT INTO user_metrics_1m
SELECT
  TUMBLE_START(row_time, INTERVAL '1' MINUTE) AS window_start,
  TUMBLE_END(row_time, INTERVAL '1' MINUTE)   AS window_end,
  user_id,
  COUNT(*) AS pv,
  COUNT(DISTINCT session_id) AS uv
FROM user_events
WHERE event_type = 'page_view'
GROUP BY
  TUMBLE(row_time, INTERVAL '1' MINUTE),
  user_id;

至此,一个完整的 Flink SQL 作业就串起来了:从 DDL → Query → DML → 运维调试

相关推荐
阿里云大数据AI技术1 天前
用 SQL 调大模型?Hologres + 百炼,让数据开发直接“对话”AI
sql·llm
大大大大晴天1 天前
Flink生产问题排障-HBase NotServingRegionException
flink·hbase
大大大大晴天2 天前
Flink生产问题排障-Kryo serializer scala extensions are not available
大数据·flink
tryCbest6 天前
数据库SQL学习
数据库·sql
cowboy2586 天前
mysql5.7及以下版本查询所有后代值(包括本身)
数据库·sql
努力的lpp6 天前
SQL 报错注入
数据库·sql·web安全·网络安全·sql注入
麦聪聊数据6 天前
统一 Web SQL 平台如何收编企业内部的“野生数据看板”?
数据库·sql·低代码·微服务·架构
山峰哥6 天前
吃透 SQL 优化:告别慢查询,解锁数据库高性能
服务器·数据库·sql·oracle·性能优化·编辑器
yumgpkpm6 天前
AI视频生成:Wan 2.2(阿里通义万相)在华为昇腾下的部署?
人工智能·hadoop·elasticsearch·zookeeper·flink·kafka·cloudera
轩情吖6 天前
MySQL初识
android·数据库·sql·mysql·adb·存储引擎