普通视图
参数化视图
Materialized View
OR REPLACE 和 IF NOT EXISTS 互斥:不能同时使用,否则会报语法错误。
CREATE OR REPLACE MATERIALIZED VIEW
CREATE OR REPLACE MATERIALIZED VIEW 会以原子方式替换现有的 materialized view 及其内部存储表 (如有) 。此操作要求使用 Atomic 或 Replicated 数据库引擎。
- 不带
TO子句:旧的内部表会被删除,并创建一个新的内部表。除非指定了POPULATE,否则内部表中的现有数据将会丢失。 - 带
TO子句:仅替换视图定义;目标表及其数据不受影响。 - 兼容
REFRESH、ON CLUSTER和所有引擎选项。POPULATE仅在Atomic数据库中受支持——在Replicated数据库中会被拒绝 (请参见下方关于POPULATE的说明) 。 - 需要
CREATE VIEW和DROP VIEW特权。
CREATE OR REPLACE MATERIALIZED VIEW 仅受 Atomic 或 Replicated 数据库引擎支持。不支持 Ordinary 数据库引擎。TO [db].[table] 的 Materialized view 时,必须指定 ENGINE,即用于存储数据的表引擎。
创建带有 TO [db].[table] 的 Materialized view 时,不能同时使用 POPULATE。
Materialized view 的实现方式如下:当向 SELECT 中指定的表插入数据时,部分新插入的数据会经过该 SELECT 查询转换,结果再插入到该视图中。
ClickHouse 中的 materialized view 在插入到目标表时使用列名,而不是列顺序。如果
SELECT 查询结果中缺少某些列名,ClickHouse 会使用默认值,即使该列不是 Nullable。安全的做法是在使用 materialized view 时为每一列都添加别名。ClickHouse 中的 materialized view 在实现上更像插入触发器。如果视图查询中包含聚合,它只会应用于刚插入的那一批数据。对源表现有数据的任何更改 (如 update、delete、drop partition 等) 都不会改变 materialized view。ClickHouse 中的 materialized view 在发生错误时不具有确定性行为。这意味着,已经写入的块会保留在目标表中,但出错后的所有块都不会保留。默认情况下,如果向某个视图写入时抛出异常,INSERT 查询将失败。此时数据块是否已经到达源表并不保证——这取决于插入管道的时序,而不是视图错误。对失败的 INSERT 使用插入去重 (insert_deduplicate、deduplicate_blocks_in_dependent_materialized_views) 进行重试,以获得对源表及所有依赖视图的 exactly-once 交付。在 INSERT 查询上设置 materialized_views_ignore_errors=true 只会改变错误报告方式:每个视图错误都会作为警告记录下来,且 INSERT 查询会成功。向失败视图的目标端交付是不完整的——异常发生前已处理的块会被保留,而失败的块及其后的所有块都会从该视图中丢弃。该目标端下游的视图只能看到实际到达的那些块,因此它们的交付也同样是不完整的。未抛出异常的同级视图 (以及它们的下游链) 会被完整写入,而源表也会照常写入。由于 INSERT 会报告成功,客户端不会收到失败信号,也不会触发自动重试;仅当源表写入绝不能被视图侧问题阻塞时,才应使用此设置 (例如 system.*_log 表) 。对于 system.*_log 表,materialized_views_ignore_errors 默认为 true。POPULATE,则在创建视图时,现有表数据会被插入到视图中,就像执行 CREATE TABLE ... AS SELECT ... 一样。否则,查询只包含在视图创建之后插入到表中的数据。我们不建议使用 POPULATE,因为在创建视图期间插入到表中的数据不会被插入到该视图中。
鉴于
POPULATE 的工作方式类似于 CREATE TABLE ... AS SELECT ...,因此它有以下限制:- 不支持 Replicated database
- 不支持 ClickHouse Cloud
INSERT ... SELECT。SELECT 查询可以包含 DISTINCT、GROUP BY、ORDER BY、LIMIT。请注意,相应的转换会在每个插入数据块上独立执行。例如,如果设置了 GROUP BY,数据会在插入期间进行 聚合,但仅限于单个插入数据包内的数据,之后不会再进一步聚合。例外情况是使用可自行执行数据 聚合 的 ENGINE,例如 SummingMergeTree。
如果 materialized view 使用 TO [db.]name 这种写法,你可以先 DETACH 该视图,对目标表执行 ALTER,然后再 ATTACH 之前已 DETACH 的视图。
请注意,materialized view 会受到 optimize_on_insert 设置的影响。数据会先合并,再插入到视图中。
视图看起来与普通表相同。例如,它们会显示在 SHOW TABLES 查询结果中。
要删除视图,请使用 DROP VIEW。不过,DROP TABLE 对 VIEW 也同样适用。
SQL 安全
DEFINER 和 SQL SECURITY 允许你指定在执行视图的底层查询时使用哪个 ClickHouse 用户。
SQL SECURITY 有三个合法取值:DEFINER、INVOKER 或 NONE。你可以在 DEFINER 子句中指定任何现有用户或 CURRENT_USER。
下表说明了从视图中查询时,哪个用户需要具备哪些权限。
请注意,无论 SQL 安全选项是什么,在所有情况下,仍然需要具备 GRANT SELECT ON <view> 才能读取该视图。
| SQL 安全选项 | 视图 | Materialized View |
|---|---|---|
DEFINER alice | alice 必须拥有该视图源表的 SELECT 权限。 | alice 必须拥有该视图源表的 SELECT 权限,以及该视图目标表的 INSERT 权限。 |
INVOKER | 用户必须拥有该视图源表的 SELECT 权限。 | 不能为 materialized view 指定 SQL SECURITY INVOKER。 |
NONE | - | - |
SQL SECURITY NONE 是一个已弃用选项。任何有权创建带有 SQL SECURITY NONE 的视图的用户,都能够执行任意查询。
因此,要使用此选项创建视图,必须具备 GRANT ALLOW SQL SECURITY NONE TO <user>。DEFINER/SQL SECURITY,则使用默认值:
如果视图在附加时未指定 DEFINER/SQL SECURITY,则 materialized view 的默认值为 SQL SECURITY NONE,普通视图的默认值为 SQL SECURITY INVOKER。
要更改现有视图的 SQL 安全设置,请使用
示例
Live View
可刷新materialized view
interval 是由若干简单时间间隔构成的序列:
REFRESH 子句必须至少指定 EVERY、AFTER 或 DEPENDS ON 之一。单独使用 REFRESH (即一个都不带) 会被拒绝。不带 EVERY/AFTER 的 REFRESH DEPENDS ON ... 是 REFRESH AFTER 0 SECOND DEPENDS ON ... 的简写;请参见下方的 刷新依赖。
周期性运行相应的查询,并将其结果存储到表中。
- 如果指定了
APPEND,每次刷新都会将行插入表中,而不会删除现有行。该 insert 不是原子的,和普通的INSERT INTO ... SELECT查询一样。 - 否则,每次刷新都会以原子方式替换表’的现有内容。
- 没有插入触发器。当新数据插入到
SELECT中指定的表时,它不会自动推送到可刷新materialized view。相反,只有在周期性刷新或手动刷新执行时才会插入数据。 - 对
SELECT查询没有限制。表函数 (例如url()) 、视图、UNION、JOIN 都允许使用。
查询中
REFRESH ... SETTINGS 部分的 settings 是刷新设置 (例如 refresh_retries) ,与常规设置 (例如 max_threads) 不同。常规设置可以在查询末尾使用 SETTINGS 指定。刷新调度
RANDOMIZE FOR 会随机调整每次刷新的时间,例如:
REFRESH EVERY 1 MINUTE 的视图完成一次 刷新 需要 2 分钟,那么它实际上就只会每 2 分钟 刷新 一次。如果之后它变快了,能在 10 秒内完成 刷新,那么它又会恢复为每分钟 刷新 一次。 (特别地,它不会为了补上错过的 刷新 而改为每 10 秒 刷新 一次——因为并不存在这样的积压。)
通常,materialized view 创建后会立即启动第一次 刷新:距离上次 刷新 的时间相当于无穷大,因此无论什么调度,都会认为现在应该刷新。如果指定了 EMPTY,则会跳过这次初始 刷新,第一次 刷新 会在下一个计划时间发生;例如,对于 EVERY 1 HOUR,第一次 刷新 会在当前小时结束时发生。
在 Replicated DB 中
APPEND 模式下,可以通过 SETTINGS all_replicas = 1 禁用协调。这样一来,各副本会彼此独立地执行刷新。在这种情况下,不要求使用 ReplicatedMergeTree。
在非 APPEND 模式下,仅支持协调刷新。若要使用非协调方式,请使用 Atomic database 和 CREATE ... ON CLUSTER 查询,在所有副本上创建可刷新 materialized view。
协调通过 Keeper 完成。znode 路径由 default_replica_path 服务器设置决定。
刷新依赖关系
DEPENDS ON 用于同步不同表的刷新:
DEPENDS ON 仅适用于可刷新materialized view 之间。特别是,如果依赖视图使用了 TO <table>,请务必使用视图名称而不是表名称。如果 DEPENDS ON 列表中包含普通表、不可刷新的视图,或者存在拼写错误,该视图将永远不会刷新,并会在 system.view_refreshes 中显示状态 MissingDependencies。可以使用 ALTER 更改或移除依赖关系,参见 更改刷新参数。使用 DEPENDS ON 保持一致的传播延迟
REFRESH EVERY,那么该依赖关系会在每个时间槽内生效。
例如,假设视图 X 和 Y 都使用 REFRESH EVERY 1 HOUR,且 Y 从 X 的输出表中读取数据。在没有依赖关系时,Y 通常会看到 X 上一小时刷新生成的数据。使用 DEPENDS ON X 后,Y 在 11:00 的刷新只有在 X 于 11:00 的刷新完成后才会开始。
使用 DEPENDS ON 进行批次流处理
REFRESH EVERY,则依赖视图 X 会在其所有依赖项自 X 上次刷新以来都至少刷新过一次后刷新。REFRESH AFTER T 会引入一个延迟:依赖视图会在依赖项完成刷新后的 T 时间开始刷新。
允许循环依赖,而且这很有用。请考虑下面这个由可刷新materialized view 构成的关系图:
- X 从某个 stream 中取出一批行,并将其写入一个表。
- 然后,Y 和 Z 都从该表读取数据,执行不同的聚合,并将结果追加到其他表中。
- 该批次完全处理完后,X 会取出下一批次,然后循环重复进行。
SYSTEM REFRESH VIEW,而不是只在创建视图后执行一次。
刷新设置
refresh_retries- 刷新查询因异常失败时的重试次数。如果所有重试都失败,则跳过并等待下一个计划刷新时间。0 表示不重试,-1 表示无限重试。默认值:2。refresh_retry_initial_backoff_ms- 如果refresh_retries不为 0,首次重试前的延迟时间。此后每次重试的延迟都会翻倍,直到达到refresh_retry_max_backoff_ms。默认值:100 毫秒。refresh_retry_max_backoff_ms- 刷新尝试之间延迟时间指数增长的上限。默认值:60000 毫秒 (1 分钟) 。all_replicas- 在带有APPEND的 Replicated database 中,控制是让所有副本独立刷新,还是在每个计划时间点仅由一个副本刷新。视图创建后不可更改。默认值:false。
更改刷新参数
ALTER TABLE ... MODIFY REFRESH 更改现有可刷新materialized view的刷新参数:
EVERY 或 AFTER) 是必填项:该语句始终会用指定内容替换所有刷新参数——包括调度、RANDOMIZE FOR、DEPENDS ON 和刷新设置。凡是未指定的内容,都会重置为默认值 (设置) 或被移除 (依赖项、随机化) 。
-
如果只想修改刷新设置 (例如
refresh_retries) ,请重复现有调度: -
materialized view 不支持
ALTER TABLE ... MODIFY SETTING refresh_retries = ...;必须通过MODIFY REFRESH进行修改。 -
不支持添加或移除
APPEND。 -
all_replicas设置在创建后无法更改。
其他操作
system.view_refreshes 中查看。具体而言,其中包含刷新进度 (如果正在运行) 、上次和下次刷新时间,以及刷新失败时的异常信息。
如需手动停止、启动、触发或取消刷新,请使用 SYSTEM STOP|START|REFRESH|WAIT|CANCEL VIEW。
如需等待刷新完成,请使用 SYSTEM WAIT VIEW。这在创建视图后等待首次刷新时尤其有用。
顺带一提:刷新查询可以从正在刷新的视图中读取数据,并看到刷新前版本的数据。这意味着你可以实现 Conway’s game of life:https://pastila.nl/?00021a4b/d6156ff819c83d490ad2dcec05676865#O0LGWTO7maUQIA4AcGUtlA==
Window View
这是一项 Experimental 功能,未来的发行版中可能会发生向后不兼容的变更。使用 allow_experimental_window_view 设置来启用 window view 和
WATCH 查询。输入命令 set allow_experimental_window_view = 1。WATCH 查询推送通知。
创建窗口视图与创建 MATERIALIZED VIEW 类似。窗口视图需要一个内部存储引擎来保存中间数据。可以使用 INNER ENGINE 子句指定内部存储引擎;如果未指定,窗口视图默认使用 AggregatingMergeTree 作为内部引擎。
创建不带 TO [db].[table] 的窗口视图时,必须指定 ENGINE——即用于存储数据的表引擎。
时间窗口函数
时间属性
time_attr 设置为表中的某一列,或使用函数 now(),来定义处理时间属性。以下查询创建了一个使用处理时间的 窗口视图。
WATERMARK 语法支持事件时间处理。
窗口视图提供三种 watermark 策略:
STRICTLY_ASCENDING:发出截至当前已观测到的最大时间戳作为 watermark。时间戳小于该最大时间戳的行不属于迟到数据。ASCENDING:发出截至当前已观测到的最大时间戳减 1 作为 watermark。时间戳等于或小于该最大时间戳的行不属于迟到数据。BOUNDED:WATERMARK=INTERVAL。发出 watermark,其值为已观测到的最大时间戳减去指定延迟。
WATERMARK 创建窗口视图:
ALLOWED_LATENESS=INTERVAL 来支持处理迟到事件。下面是一个处理迟到事件的示例:
ALTER TABLE ... MODIFY QUERY 语句来修改窗口视图中定义的 SELECT 查询。新的 SELECT 查询产生的数据结构,无论是否包含 TO [db.]name 子句,都应与原始 SELECT 查询保持一致。请注意,当前窗口中的数据将会丢失,因为中间状态无法复用。
监视新窗口
TO 语法将结果输出到表中。
LIMIT,以设置在终止查询前接收更新的次数。EVENTS 子句可用于获取 WATCH 查询的简写形式:此时返回的不再是查询结果,而仅是最新的查询水位线。
设置
window_view_clean_interval:窗口视图的清理间隔,单位为秒,用于释放过期数据。系统会根据系统时间或WATERMARK配置保留尚未被完全触发的窗口,其他数据将被删除。window_view_heartbeat_interval:心跳间隔,单位为秒,用于表明 watch 查询仍处于活动状态。wait_for_window_view_fire_signal_timeout:在事件时间处理中等待窗口视图触发信号的超时时间。
示例
data 的日志表中每 10 秒内的点击日志数量,其表结构如下:
WATCH 查询获取结果。
data 时,
WATCH 查询应输出如下结果:
TO 语法将输出写入另一张表。
*window_view*) 。
窗口视图用法
- 监控:按时间对指标日志进行聚合和计算,并将结果输出到目标表。仪表板可以将目标表用作源表。
- 分析:在时间窗口内自动聚合并预处理数据。这在分析大量日志时非常有用。预处理可消除多个查询中的重复计算,并降低查询延迟。
临时视图
- 会话生命周期 临时视图仅在当前会话期间存在。会话结束后会自动删除。
- 无数据库 不能使用数据库名限定临时视图。它存在于数据库之外 (会话命名空间) 。
-
不复制 / 不支持 ON CLUSTER
临时对象仅在当前会话内有效,不能通过
ON CLUSTER创建。 - 名称解析 如果临时对象 (表或视图) 与持久对象同名,且某个查询在不带数据库名的情况下引用该名称,则会使用临时对象。
-
逻辑对象 (无存储)
临时视图仅存储其
SELECT文本 (内部使用View存储) 。它不会持久化数据,也不接受INSERT。 -
Engine 子句
无需指定
ENGINE;如果写为ENGINE = View,也会被忽略,并视为相同的逻辑视图。 -
安全 / 特权
创建临时视图需要
CREATE TEMPORARY VIEW特权,而CREATE VIEW会隐式授予该特权。 -
SHOW CREATE
使用
SHOW CREATE TEMPORARY VIEW view_name;可输出临时视图的 DDL。
语法
OR REPLACE 不支持用于临时视图 (与临时表一致) 。如果你需要“替换”临时视图,请先将其删除,再重新创建。
示例
不允许的用法 / 限制
CREATE OR REPLACE TEMPORARY VIEW ...→ 不允许 (请使用DROP+CREATE) 。CREATE TEMPORARY MATERIALIZED VIEW .../WINDOW VIEW→ 不允许。CREATE TEMPORARY VIEW db.view AS ...→ 不允许 (不支持数据库限定) 。CREATE TEMPORARY VIEW view ON CLUSTER 'name' AS ...→ 不允许 (临时对象仅在当前会话内有效) 。POPULATE、REFRESH、TO [db.table]、内部引擎以及所有 MV 专用子句 → 不适用于临时视图。
关于分布式查询的说明
Memory) ,其数据在分布式查询执行期间也可以像临时表一样传送到远程服务器。