Skip to main content
创建一个新视图。视图可以是普通、materialized和可刷新的 materialized。

普通视图

语法:
普通视图不存储任何数据。每次访问时,它们只是从另一张表中读取数据。换句话说,普通视图无非就是一个已保存的查询。从视图读取时,这个已保存的查询会作为子查询用于 FROM 子句中。 例如,假设你已经创建了一个视图:
并编写了一个查询:
此查询与使用该子查询完全等同:

参数化视图

参数化视图与普通视图类似,但可以在创建时定义不会立即解析的参数。 这类视图可与表函数配合使用:将视图名称作为函数名,将参数值作为其参数传入。
上述内容会为该表创建一个视图,通过替换参数,可按如下方式将其用作表函数。
由于参数化视图依赖于参数值,未提供参数时便没有 schema。 这意味着 system.columns 表中没有参数化视图的相关信息。 此外,只有提供参数后,DESCRIBE 查询才能执行。

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 数据库引擎。
示例:
以下是使用 Materialized views 的分步指南。
Materialized views 用于存储由相应 SELECT 查询转换后的数据。 创建不带 TO [db].[table] 的 Materialized view 时,必须指定 ENGINE,即用于存储数据的表引擎。 创建带有 TO [db].[table] 的 Materialized view 时,也可以使用 POPULATE 从现有源数据回填目标表 (目标表可能已包含数据,此时回填的行会被追加) 。POPULATE 不能与 REFRESH 结合使用:可刷新的 可刷新materialized view 会在首次刷新时填充,因此 POPULATE 会将初始数据加载两次 (请改用 EMPTY 跳过首次刷新) 。 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 MATERIALIZED VIEW,POPULATE 默认具有原子性 (设置 materialized_views_populate_atomically = 1) :在源表上短暂持有排他锁的同时,视图会订阅源表的新插入,并对现有数据创建快照,因此在填充期间并发插入的每一行都会被恰好一次交付到视图中——既不会遗漏,也不会重复。随后,填充过程 (可能耗时较长) 会读取固定快照,而不持有任何锁。 这是本地插入路径的原子性:排他锁仅会与在同一服务器上获取此源表存储锁的插入操作串行化,因此 exactly-once 保证仅覆盖通过此服务器到达的插入。这并非集群范围的保证——在填充期间并发插入到 ReplicatedMergeTree 源表的另一副本上的行,或通过分布式写入路径 (例如插入到 Distributed 表或通过 ON CLUSTER) 插入的行,不在此范围内,仍可能被遗漏或重复。 如果填充失败——例如,无法在 lock_acquire_timeout 内获取繁忙源表上的排他锁,或者视图的 SELECT 在运行时抛出异常——则刚创建的视图会被删除,且 CREATE 查询会失败,不会留下其创建的任何内容,因此可以直接重试。对于 TO [db].[table] 形式,此回滚仅删除视图,绝不会删除预先存在的目标表——但失败的填充已插入目标表的行会保留在其中,就像向该表执行失败的 INSERT ... SELECT 后一样,因此重试 CREATE 会再次插入这些行。如果回填必须精确无误,请重试插入到已截断或全新的目标表中,或者使用如 ReplacingMergeTree 这样的去重引擎。
原子性要求源表支持读取固定的时间点快照——即 MergeTree 家族和 Memory。对于任何其他源 (视图、Distributed、Merge、Buffer、Log 家族,或不在 Atomic database 中的表),填充会回退到旧版非原子行为 (记录在 server log 中):现有数据会通过单独且未协调的快照读取,因此在填充期间插入的行可能会遗漏或重复。在这种情况下,如果需要精确数据,请创建视图并单独运行 INSERT ... SELECT。设置 materialized_views_populate_atomically = 0 会对所有源强制使用此旧版行为。原子填充仅适用于普通的 CREATE MATERIALIZED VIEW。CREATE OR REPLACE / REPLACE MATERIALIZED VIEW ... POPULATE 始终使用旧版非原子填充。POPULATE 不支持 Replicated databases (使用 database_replicated_allow_heavy_create 覆盖此限制),且不支持 ClickHouse Cloud。通过该覆盖启用时,填充始终为旧版非原子填充——失败的填充无法在所有副本上一致地回滚。
SELECT 查询可以包含 DISTINCT、GROUP BY、ORDER BY、LIMIT。请注意,相应的转换会在每个插入数据块上独立执行。例如,如果设置了 GROUP BY,数据会在插入期间进行 聚合,但仅限于单个插入数据包内的数据,之后不会再进一步聚合。例外情况是使用可自行执行数据 聚合 的 ENGINE,例如 SummingMergeTree。 如果 materialized view 使用 TO [db.]name 这种写法,你可以先 DETACH 该视图,对目标表执行 ALTER,然后再 ATTACH 之前已 DETACH 的视图。 视图看起来与普通表相同。例如,它们会显示在 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 SECURITY NONE 是一个已弃用选项。任何有权创建带有 SQL SECURITY NONE 的视图的用户,都能够执行任意查询。 因此,要使用此选项创建视图,必须具备 GRANT ALLOW SQL SECURITY NONE TO <user>。
如果未指定 DEFINER/SQL SECURITY,结果取决于 ignore_empty_sql_security_in_create_view_query 服务器设置。 当其默认值为 true 时,查询会按原样存储,且视图会获得空的 SQL 安全类型。普通视图随后将使用调用者的权限运行;对于显式指定目标表的 materialized view,将跳过对该目标表的访问检查:向源表插入数据不需要目标表的 INSERT 权限,从视图中读取数据也不需要其 SELECT 权限。 当值为 false 时,会在创建时将以下默认值写入视图定义: 无论该设置为何值,可刷新materialized view 始终会获得这些默认值。 视图在服务器启动时附加或重新加载时,会保留其存储定义中的 SQL 安全类型,因此,未指定 DEFINER/SQL SECURITY 而存储的视图会保留空的 SQL 安全类型。 要更改现有视图的 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 查询一样。
  • 如果指定了 APPEND INCREMENTAL,每次刷新只会对自上次刷新以来已提交到源表的行运行查询,并追加其结果。
  • 否则,每次刷新都会以原子方式替换表’的现有内容。
与常规不可刷新的 materialized view 的区别:
  • 没有插入触发器。当新数据插入到 SELECT 中指定的表时,它不会自动推送到可刷新materialized view。相反,只有在周期性刷新或手动刷新执行时才会插入数据。
  • 对 SELECT 查询没有限制。表函数 (例如 url()) 、视图、UNION、JOIN 都允许使用。APPEND INCREMENTAL 是唯一的例外:它要求源表是单个普通 MergeTree 表,并且设置了 enable_block_number_column = 1 和 enable_block_offset_column = 1,同时不允许 JOIN、UNION、子查询、视图和表函数。
查询中 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 中

如果可刷新 materialized view 位于 Replicated database 中,各副本会相互协调,从而确保在每个计划的刷新时间点只有一个副本执行刷新。这里要求使用 ReplicatedMergeTree 表引擎,这样所有副本都能看到刷新生成的数据。 在 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 构成的关系图:
  1. X 从某个 stream 中取出一批行,并将其写入一个表。
  2. 然后,Y 和 Z 都从该表读取数据,执行不同的聚合,并将结果追加到其他表中。
  3. 该批次完全处理完后,X 会取出下一批次,然后循环重复进行。
完整示例:
更长的事件链也同样适用。 只有在启用刷新协调时,这种方式才能正常工作,也就是说,这些视图位于 Replicated 或 Shared database 中。如果没有协调机制,服务器重启会中断这一循环,因此每次重启后都需要手动执行一次 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 和 INCREMENTAL 既不能添加也不能移除。
  • all_replicas 设置在创建后无法更改。
示例:

其他操作

所有可刷新materialized view 的状态都可在表 system.view_refreshes 中查看。具体而言,其中包含刷新进度 (如果正在运行) 、上次和下次刷新时间,以及刷新失败时的异常信息。 如需手动停止、启动、触发或取消刷新,请使用 SYSTEM STOP|START|REFRESH|WAIT|CANCEL VIEW。 如需等待刷新完成,请使用 SYSTEM WAIT VIEW。这在创建视图后等待首次刷新时尤其有用。
顺带一提:刷新查询可以从正在刷新的视图中读取数据,并看到刷新前版本的数据。这意味着你可以实现 Conway’s game of life:https://pastila.nl/?00021a4b/d6156ff819c83d490ad2dcec05676865#O0LGWTO7maUQIA4AcGUtlA==

相关内容

临时视图

ClickHouse 支持临时视图,其特性如下 (在适用情况下与临时表一致) :
  • 会话生命周期 临时视图仅在当前会话期间存在。会话结束后会自动删除。
  • 无数据库 不能使用数据库名限定临时视图。它存在于数据库之外 (会话命名空间) 。
  • 不复制 / 不支持 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 不支持用于临时视图 (与临时表一致) 。如果你需要“替换”临时视图,请先将其删除,再重新创建。

示例

创建一个临时源表,并基于它创建一个临时视图:
查看其 DDL:
将其删除:

不允许的用法 / 限制

  • CREATE OR REPLACE TEMPORARY VIEW ... → 不允许 (请使用 DROP + CREATE) 。
  • CREATE TEMPORARY MATERIALIZED VIEW ... → 不允许。
  • CREATE TEMPORARY VIEW db.view AS ... → 不允许 (不支持数据库限定) 。
  • CREATE TEMPORARY VIEW view ON CLUSTER 'name' AS ... → 不允许 (临时对象仅在当前会话内有效) 。
  • POPULATE、REFRESH、TO [db.table]、内部引擎以及所有 MV 专用子句 → 不适用于临时视图。

关于分布式查询的说明

临时视图只是一个定义,本身不包含可传递的数据。如果临时视图引用了临时表 (例如 Memory) ,其数据在分布式查询执行期间也可以像临时表一样传送到远程服务器。

示例

最后修改于 2026年9月26日