本系列基于 SQLMesh 官方文档整理,讲三种"不走常规计算路径"的模型类型,每篇一种:
- 第(一)篇:EMBEDDED ------ 不产生任何表/视图,逻辑内联进下游查询
- 第(二)篇:EXTERNAL ------ 只登记外部表的列信息,模型本身不会被运行
- 第(三)篇:MANAGED(本篇)------ 数据由数据库引擎的后台进程自动保持最新
主要参考:
一、它解决什么问题
常规表需要你自己负责数据更新。但有些引擎支持一种"引擎自己保证表内数据是最新的 "的表:它基于一个读取其他表的查询构建,一旦被读的基表发生变化,数据库就自动让这张托管表反映这些变化,用户不需要做任何额外操作(尤其不需要手动发 REFRESH)。
官方描述其底层机制:"most of them have background processes that run and automatically keep the tables up to date, within the parameters you define when you create the table."
MANAGED 模型就是把这种引擎能力暴露给 SQLMesh,其含义是:
This indicates to SQLMesh that the underlying database engine will ensure that the data remains up to date and all SQLMesh needs to do is maintain the schema.
即:引擎负责数据新鲜度,SQLMesh 只负责维护 schema。
官方 Model kinds 页同时给出行为差异:
These models don't get updated with new intervals or refreshed when
sqlmesh runis called. Responsibility for keeping the data up to date falls on the engine.
也就是说,调用 sqlmesh run 时,MANAGED 模型不会 按新时间区间被增量计算,也不会被刷新。

二、与物化视图的区别(必考点)
SQLMesh 早就支持物化视图(kind VIEW ( materialized true )),那为什么还需要 MANAGED?差别在语义,且在部分引擎下两者其实没有区别。
物化视图的局限(依引擎而定):
- 查询通常只能派生自单个基表;
- 引擎不会自动维护 ,需要手动发
REFRESH MATERIALIZED VIEW或等价命令才能刷新数据。
MANAGED 模型的三点不同:
- 基表变化时,引擎自动更新表数据;
- 更新时引擎对查询具备语义理解,能自行判断该做增量刷新还是全量刷新;
- 无需手动
REFRESH,引擎在后台进程透明维护。
顺带回顾:SQLMesh 的
VIEW若设materialized true,仅对支持物化视图的引擎生效(BigQuery、Databricks、Snowflake),且只有当渲染出的查询与上次建视图时的查询不一致、或目标视图不存在时才会重建。
三、最关键的实践结论:应当基于 EXTERNAL 模型构建
本系列前两篇在这里闭环了。官方原文:
"managed models would typically be built off an External Model rather than another SQLMesh model."
理由讲得很直白:
"Since SQLMesh already ensures that models it's tracking are kept up to date, the main benefit of managed models comes when they read from external tables that aren't tracked by SQLMesh."
拆开理解:
- SQLMesh 已经在保证它自己跟踪的模型 是最新的,你再套一层引擎托管,属于重复劳动;
- MANAGED 的真正收益出现在"读取 SQLMesh 不跟踪的外部表"时------外部表一直在变,而你需要一张永远跟得上它的表,这时让引擎后台刷新来做最合适。
于是三篇串成一条完整的常见链路:
外部系统表
│
├─ EXTERNAL(第 2 篇):登记列信息 → 打通列级血缘、类型推断、上游审计
│
├─ MANAGED(本篇):读取它 → 引擎自动保持数据新鲜,无需自己调度
│
└─ 常规 SQLMesh 模型(FULL / INCREMENTAL_BY_TIME_RANGE ...):做正式加工
四、支持情况与写法示例
⚠️ 前提警告(官方原文置于页面顶部):
"Managed models are still under development and the API / semantics may change as support for more engines is added"
即仍在开发中,随着更多引擎接入,API 与语义可能变化。
当前实现的引擎:
| 引擎 | 底层实现 |
|---|---|
| Snowflake | Dynamic Table |
(其余引擎暂未实现------这正是下文"可移植性差"的直接原因。)
4.1 完整示例(Snowflake Dynamic Table)
模型定义:
sql
MODEL (
name db.events,
kind MANAGED,
physical_properties (
warehouse = datalake,
target_lag = '2 minutes',
data_retention_time_in_days = 2
)
);
SELECT
event_date::DATE as event_date,
event_payload::TEXT as payload
FROM raw_events
SQLMesh 实际下发到引擎的命令:
sql
CREATE OR REPLACE DYNAMIC TABLE db.events
WAREHOUSE = "datalake",
TARGET_LAG = '2 minutes'
DATA_RETENTION_TIME_IN_DAYS = 2
AS
SELECT
event_date::DATE as event_date,
event_payload::TEXT as payload
FROM raw_events
4.2 务必注意:不要加时间过滤的 WHERE
官方专门加了 Note:
"SQLMesh will not create intervals and run this model for each interval, so there is no need to add a WHERE clause with date filters like you would for a normal incremental model. How the data in this model is refreshed is completely up to Snowflake."
新手最容易犯的错就是照抄增量模型写法,加上 WHERE event_date BETWEEN @start_ds AND @end_ds------MANAGED 不需要,也不该这么写。
4.3 physical_properties 与引擎参数
由于没有统一标准 ,各厂商实现、语义、配置参数都不同,SQLMesh 通过 physical_properties 把引擎专属参数透传给适配器(官方说明见 Model kinds 页与模型概览页的 physical_properties)。
Snowflake Dynamic Table 中,以下属性设在模型的 physical_properties 上:
| Snowflake 属性 | 是否必填 | 说明 |
|---|---|---|
target_lag |
必填 | 数据新鲜度目标 |
warehouse |
否 | Snowflake 侧本是必填项;若不在 SQLMesh 指定,SQLMesh 会取 select current_warehouse() 的结果 |
refresh_mode |
否 | 刷新模式 |
initialize |
否 | 初始数据何时填充 |
data_retention_time_in_days |
否 | 数据保留天数 |
max_data_extension_time_in_days |
否 | 最大数据延展天数 |
另有属性直接写在模型顶层而非 physical_properties:
| Snowflake 属性 | 写法 |
|---|---|
CLUSTER BY |
clustered_by 是标准模型属性 ,直接在模型上设置 clustered_by 即可为 Dynamic Table 加上 CLUSTER BY 子句 |
完整属性清单见 Snowflake 的 CREATE DYNAMIC TABLE 文档。
五、生命周期:仍享受 SQLMesh 治理能力
MANAGED 模型遵循与其他模型相同的生命周期,这是它仍值得用的重要原因:
- 创建虚拟环境 = 生成指向当前模型快照的指针;
- 修改模型 → 产生新快照;
- 上游变更 → 产生新快照;
- 可通过常规的**指针交换(pointer swap)**机制部署与回滚;
- TTL 过期后快照被清理。
所以官方说法是:即便需要用 Managed 模型,你依然保有 SQLMesh 的其他收益,例如在虚拟环境中使用它。
⚠️ 一个必须知道的风险:dev 预览用的是普通表
因为托管表通常有厂商附加成本(官方举例:Snowflake 对 Dynamic Tables 有额外费用),SQLMesh 尽量避免不必要地创建托管表:
"in forward-only plans we just create a normal table to preview the changes and only re-create the managed table on deployment to prod."
即 forward-only plan 场景下,dev 环境预览变更时只建一张普通表,直到部署到 prod 才真正重建为 managed 表。
官方由此给出 Warning:
"Due to the use of normal tables for dev previews, it is possible to write a query that uses features that are available to normal tables in the target engine but not managed tables. This could result in a scenario where a plan works in a dev environment but fails when deployed to production."
翻译成人话:你可能写出一个"普通表支持、但托管表不支持"的查询,导致 plan 在 dev 一切正常、部署到生产才失败。 官方认为这点代价值得,但如果给你造成困扰欢迎去反馈。
实践对策:
- 涉及 MANAGED 的变更,不要把 dev 验证通过当成最终结论,生产发布窗口要重点复核;
- 上线前对照引擎的 Dynamic Table 支持范围自查查询语法;
- 保留一条"降级为常规模型"的回退方案(比如等价的
INCREMENTAL_BY_TIME_RANGE版本),生产失败时可快速切回。
六、适用与不适用清单
适合:
- 需要持续反映外部/非 SQLMesh 跟踪源表的数据,且引擎原生支持自动维护;
- 想省掉调度负担------不必自己的批处理作业反复刷这张表;
- 对数据新鲜度有明确 SLA(用
target_lag表达),且能接受厂商额外费用; - 同时希望保留 SQLMesh 的版本、部署、回滚、虚拟环境等治理能力。
不适合 / 应谨慎:
- 可移植性差是首要顾虑 :官方明确 "MANAGED models are not as portable between database engines as other SQLMesh model types",因为没有标准,各厂商语义与参数都不同。若项目有多引擎迁移可能,慎用;
- 状态可见性受限 :官方指出 "due to their black-box nature, SQLMesh has limited visibility into the integrity and state of the model" ------黑盒性质导致 SQLMesh 对该模型的完整性与状态了解有限,数据质量保障弱于自己计算落表的模型;
- 官方默认建议 :"We would recommend using standard SQLMesh model types in the first instance." 优先用标准类型,确实需要才用 MANAGED;
- 仍在开发中,API/语义可能变化;
- Python 模型不支持
MANAGED(官方 Note,须改用 SQL 模型)。
七、三种类型终极对照
把本系列三篇收在一张表上:
| 维度 | EMBEDDED | EXTERNAL | MANAGED |
|---|---|---|---|
| 本质 | 可复用 SQL 片段,编译期内联 | 外部表的元数据登记 | 引擎托管的表 |
| 数仓里有对象吗 | ❌ 无表无视图 | ❌ 无(表本来就在外面) | ✅ 有(引擎建的托管表) |
| 谁维护数据 | 不适用(不落地) | 外部系统 | 数据库引擎的后台进程 |
sqlmesh run 会计算它吗 |
❌ 不会 | ❌ 没有可运行的查询 | ❌ 不按区间增量/刷新 |
| 定义位置 | models/*.sql |
根目录 external_models.yaml / external_models/*.yaml |
models/*.sql |
| kind 专属参数 | 无 | YAML 字段:name/description/gateway/columns/audits |
靠 physical_properties 透传引擎参数 |
| 主要收益 | 复用逻辑、零对象膨胀 | 列级血缘、类型推断、上游审计 | 免调度、自动新鲜度、仍可用虚拟环境 |
| 主要风险 | 逻辑重/引用多时成本随下游增长 | YAML 与实际表结构漂移 | 可移植性差、状态黑盒、厂商成本、dev/prod 校验差异 |
| Python 模型支持 | ❌ | 不适用 | ❌ |
选型口诀
- 逻辑要复用,但不想在数仓里多一个对象 → EMBEDDED;
- 表不归 SQLMesh 管、只是要读它 → EXTERNAL 登记列信息(顺手加上审计);
- 表要由引擎负责保持新鲜 ,且读的是不受 SQLMesh 管理的外部表 → MANAGED(先确认引擎已支持);
- 需要自己定义计算逻辑并按节奏刷新 → 老实用最基础的三类:小表
FULL、事件/日志/交易INCREMENTAL_BY_TIME_RANGE、有主键要 upsert 用INCREMENTAL_BY_UNIQUE_KEY。
八、小结
- MANAGED 把"计算与刷新"的控制权交给引擎,SQLMesh 只维护 schema;
- 它与物化视图的差别在于:跨多基表、引擎有语义理解、无需手动 REFRESH;
- 最佳搭档是 EXTERNAL------读不受 SQLMesh 跟踪的外部表才是它的价值所在;
- 仍享有快照、指针交换、回滚、虚拟环境等治理能力;
- 三大代价:可移植性差、状态黑盒、厂商额外成本;以及 dev 用普通表预览可能导致"生产才失败";
- 官方立场:优先用标准模型类型,确有需要再用 MANAGED。
回到入门系列那条一以贯之的心法------SQLMesh 的能力来自它掌握"模型的计算方式和状态":
- EMBEDDED 放弃"独立资产",换取代码复用;
- EXTERNAL 放弃"数据控制权",换取血缘与质量可见性;
- MANAGED 放弃"计算控制权",换取免调度的新鲜度,代价是可见性与可移植性下降。
理解了这组"控制权 vs 收益"的权衡,这三种特殊类型就再也不会选错了。
参考资料:SQLMesh 官方文档 --- Managed models、Model kinds、Model configuration