dbt+SQLServer构建数据仓库(13):运维 FAQ 篇

dbt+SQLServer构建数据仓库(13):运维 FAQ 篇

这是 FAQ 系列第三篇,聚焦 dbt 的日常运维和实操问题:run 与 test 的关系、依赖查询、并发配置、SQL Server 特有差异、版本回滚等。都是项目跑起来之后一定会碰到的问题。


项目背景速览

  • 数据源business_db(SQL Server 业务库,含 customers / orders / products 三张表)
  • 分层架构:staging(贴源)→ core(Data Vault,增量)→ mart(星型集市)
  • 当前配置threads: 1 串行执行,dev 环境直接连 SQL Server 实例

FAQ 1:dbt test 和 dbt run 是什么关系?要先跑哪个?

答:run 负责"建表/装数据",test 负责"查数据对不对",两者是独立的命令,但推荐一起用。

两者的职责

命令 做什么 会不会改表
dbt run 编译模型 SQL,在数据库里创建/更新表和视图 ✅ 会改
dbt test 运行测试断言(schema test + data test),输出通过/失败 ❌ 只读

推荐的执行顺序

dbt run → dbt test

先跑 run,再跑 test。原因很简单:测试要检查表的数据,如果表还不存在或者数据是旧的,测试就没意义了。

更省心的方式:dbt build

dbt build

dbt build 会自动按依赖顺序执行:对每个模型,先 run 再 test,上游模型测试失败会阻断下游 run

举个例子,执行 dbt build --select stg_customers+ 时:

① stg_customers  →  ② run stg_customers  →  ③ test stg_customers
                                                          │
                                            测试通过 → 继续往下游跑
                                            测试失败 → 下游直接跳过

这比手动 dbt run && dbt test 更智能——它能在 DAG 层面做到"上游有问题,下游不浪费时间"。

日常使用建议

  • 开发调试:单独用 dbt run --select 模型名,快速迭代
  • 提交前自查:用 dbt build --select +模型名+,确保上下游都没问题
  • 生产调度:用 dbt build 全量跑,测试失败告警

FAQ 2:测试失败了会怎么样?会影响已经跑好的表吗?

答:测试失败只是"报告问题",不会自动回滚或修改数据。已经跑好的表该什么样还是什么样。

dbt test 失败时发生了什么

  1. dbt 执行测试 SQL(比如查有没有 NULL、有没有重复)
  2. 如果查到了不符合预期的数据,就在终端输出红色的 FAIL
  3. 告诉你失败了几条、哪条测试、对应的模型
  4. 到此为止——表不会被删、数据不会被回滚、进程继续跑其他测试

一个形象的类比

dbt run = 厨师炒菜(改变菜品)
dbt test = 质检员尝菜(只尝不动)

菜炒咸了 → 质检员说"不合格" → 但菜还是那盘咸的菜
                            → 厨师要不要重做,是人决定的

不同场景下的失败处理

场景 行为 结果
dbt test 单独跑 报告失败,表不动 数据是旧的/错的
dbt run && dbt test run 成功了,test 失败 新数据已经写进去了,但质量不达标
dbt build 上游模型测试失败 → 下游模型不跑 下游保持旧数据,上游已经被改了

那测试失败了怎么办?

  1. 先定位原因:看失败的是哪条测试,数据哪里不对
  2. 修复模型 SQL 或源数据:根据原因改代码或找上游
  3. 重跑对应模型dbt run --select 模型名
  4. 重跑测试确认dbt test --select 模型名

注意:dbt build 虽然能阻断下游,但不会回滚当前模型。比如 stg_customers run 成功了但 test 失败,stg_customers 的表已经是新数据了,只是下游不会接着跑。


FAQ 3:怎么看某个模型依赖了谁 / 被谁依赖?

答:主要有三种方式:dbt ls 命令行、dbt docs 可视化图、以及直接看 SQL 里的 ref()。

方式一:dbt ls(命令行,最快)

# 看 hub_customer 上游依赖了谁(左边 + 号)
dbt ls --select +hub_customer

# 看 hub_customer 被谁依赖(右边 + 号)
dbt ls --select hub_customer+

# 上下游一起看
dbt ls --select +hub_customer+

输出大概长这样:

model.dbt_sqldv.stg_customers
model.dbt_sqldv.hub_customer
model.dbt_sqldv.sat_customer
model.dbt_sqldv.dim_customer
model.dbt_sqldv.dws_order_wide

方式二:dbt docs(可视化,最直观)

dbt docs generate    # 生成文档
dbt docs serve       # 启动本地网页查看

打开后在 DAG 图里可以:

  • 点任意一个模型,高亮它的上下游
  • 看每个模型的描述、字段、测试
  • 看编译后的 SQL

方式三:直接读 SQL 代码

在模型文件里搜 ref()source()

-- sat_customer.sql 里有这两个引用
FROM {{ ref('stg_customers') }}
-- 还有 JOIN hub 的逻辑...

直接看代码能知道具体依赖了哪些字段,比命令行更细。

常用场景

场景 命令
改了 stg_customers,想知道哪些下游要跟着改 dbt ls --select stg_customers+
dim_customer 跑失败了,想知道上游有哪些 dbt ls --select +dim_customer
想确认某个模型在 DAG 第几层 dbt ls --output json --select dim_customer | jq '.depends_on'

FAQ 4:threads 调大了会不会出问题?比如死锁或者资源不够?

答:适当调大没问题,能加快速度;但调得过大确实可能出问题。

threads 是什么

threads 是 dbt 的并发数,控制同一时刻最多有几个模型在跑。

你们当前 profiles.yml 里是:

dev:
  threads: 1   # 串行,一个跑完再跑下一个

调大的收益

dbt 同一批次(同一层)内的模型没有依赖关系,可以并行跑。比如 staging 层有 3 个模型:

  • threads: 1 → 一个一个跑,总时间 = 3 个模型时间之和
  • threads: 3 → 三个一起跑,总时间 ≈ 最慢那个的时间

对于你们项目,threads 调到 3 或 4 比较合理——每层最多也就 4 个模型(core 第 3 批)。

可能出的问题

1. 数据库资源压力

  • CPU、内存、IO 飙升
  • 如果 SQL Server 和其他业务共用一台机器,可能影响业务
  • 解决:先从 2-4 开始试,观察数据库负载

2. 死锁(不太常见,但有可能)

  • 两个模型同时读写同一张源表,在特定隔离级别下可能死锁
  • SQL Server 的死锁检测会自动杀掉其中一个
  • 解决
    • 大部分情况不会有,因为 dbt 的写入目标表是不同的
    • 真碰到了,可以用 dbt run --select 模型名 单独跑失败的那个
    • 或者调低 threads

3. 连接数不够

  • 每个 thread 占用一个数据库连接
  • SQL Server 默认连接数很多,一般不会不够
  • 但如果数据库有连接数限制,要注意

实操建议

环境 推荐 threads 原因
本地开发 2-4 快一点,同时不压爆库
测试环境 4-8 可以大胆一点
生产环境 4-16 看数据库承载能力,慢慢往上加

经验法则:从 2 开始,每次翻倍,观察数据库 CPU 和 IO。到了瓶颈就往回退一档。


FAQ 5:SQL Server 上跑 dbt 和在 BigQuery / Snowflake 上有什么不一样的地方吗?

答:dbt 的核心用法(模型、ref、test、deps)都一样,但在方言、物化方式、驱动、权限上有不少差异。

1. SQL 方言不同

不同数据库的 SQL 语法有差异,dbt adapter 会处理大部分,但写复杂 SQL 时要注意:

功能 SQL Server BigQuery / Snowflake
日期转换 CONVERT(VARCHAR, date_col, 126) FORMAT_DATE() / TO_VARCHAR()
字符串拼接 'a' + 'b' CONCAT('a', 'b')
分页 OFFSET ... ROWS FETCH NEXT ... LIMIT ... OFFSET ...
窗口函数 支持 支持(语法略有差异)
递归 CTE 支持 支持

你们项目里的 macro(比如 generate_hashkey)就是针对 SQL Server 写的,换到另一个数据库不能直接用。

2. Materialization 策略差异

  • Snowflake / BigQuery:因为是云原生数仓,table 重建非常快(列式存储 + 分布式计算),很多项目全用 table 也不慢。
  • SQL Server:传统行存数据库,大表全量重建成本高,所以更依赖 incremental

你们 core 层全部用 incremental,很大程度上也是 SQL Server 环境下的务实选择。

3. 连接方式不同

  • Snowflake / BigQuery:走 HTTP/REST API,配置账号密码或密钥
  • SQL Server:走 TCP(1433 端口),需要 ODBC 驱动

你们 profiles.yml 里配置了:

driver: "ODBC Driver 18 for SQL Server"
encrypt: false
trust_server_certificate: true

这些都是 SQL Server 特有的配置。

4. 内置函数和 macro 差异

dbt 提供了很多全局 macro(比如 dbt_utils 包),大部分跨数据库兼容,但少数不行。自己写 macro 时如果用了数据库特有的函数,就只能在对应数据库上跑。

5. 权限模型

  • Snowflake / BigQuery:权限体系很精细(warehouse / database / schema / table 多层)
  • SQL Server:传统的登录名 → 用户 → 角色 → 权限那一套

dbt 项目本身不管理数据库权限(那是 DBA 的活),但你得有对应的建表、读写权限才能跑。

6. 成本模型

  • Snowflake / BigQuery:按计算量或存储量付费,dbt 跑得多花得多
  • SQL Server:一般是固定成本(买了服务器/授权),跑多跑少不直接加钱

这也导致使用习惯不一样:云数仓会更注意节省计算资源,SQL Server 反而可以放开了跑。

对开发的影响

好消息是:90% 的 dbt 用法是通用的。模型写法、ref/source、测试、依赖管理、文档——这些在哪个数据库上都一样。真正需要注意的就是你写的 SQL 本身和数据库特有函数。


FAQ 6:怎么回滚到上一个版本的模型?dbt 有版本控制吗?

答:dbt 本身不提供"一键回滚",回滚主要靠 Git 版本控制 + 全量重建。

dbt 为什么没有版本回滚?

dbt 是一个转换工具,不是数据库的版本管理系统。它的理念是:

"代码是唯一真相来源。只要代码对,重新跑一遍就能得到正确的数据表。"

所以回滚的思路是:回滚代码 → 重跑模型

回滚操作步骤

场景一:代码写错了,想回到上一个版本

# 1. 用 git 回退代码
git log --oneline               # 找到要回退的 commit
git checkout <commit-hash> -- models/   # 只回退模型文件

# 2. 重跑受影响的模型
dbt run --select 模型名
# 或者全量重建
dbt run --full-refresh --select 模型名

场景二:增量模型跑坏了,数据乱了

# 强制全量重建,把表干掉重跑
dbt run --full-refresh --select sat_customer

这是最常用的"回滚"方式——不是回退到旧数据,而是用正确的逻辑从头重建

场景三:整个项目想回到之前某个状态

git checkout <commit-hash>      # 整个项目回退
dbt run --full-refresh          # 全量重建

增量模型的"回滚陷阱"

这里有个容易踩的坑:增量模型的代码回退了,数据不会自动回退

比如:

v1 版本跑了 100 条数据 → 表中有 100 条
v2 版本加了逻辑,又增量跑了 50 条 → 表中有 150 条
代码回退到 v1 → 再增量跑 → 表中还是 150 条(不会删掉那 50 条)

解决办法:回退代码后,用 --full-refresh 全量重建一次。

生产环境的最佳实践

  1. 代码用 Git 管:每次上线打 tag,出问题能快速定位
  2. 增量模型定期全量刷:比如每周一次 --full-refresh,纠正各种数据偏差
  3. 不同环境分开:dev → test → prod,逐级验证
  4. 重要表做快照:用 dbt snapshot 记录历史状态,出问题有据可查

一句话总结

dbt 没有"撤销键",但 Git + --full-refresh 就是它的版本回滚方案。


小结

这 6 个问题,都是 dbt 项目从"跑起来"到"稳定运行"必然会遇到的运维话题:

  • run 与 test 的关系:理解清楚才能搭出靠谱的数据质量流程
  • 依赖查询:改代码前先查影响范围,避免改了一个崩了一串
  • 并发配置:在安全和速度之间找平衡
  • 数据库差异:知道哪些是 dbt 通用的、哪些是 SQL Server 特有的
  • 版本回滚:出了问题心里有底,知道怎么恢复

运维意识到位了,dbt 项目才能从"个人学习项目"变成"生产级数据管道"。


记录时间:2026-08-05
环境:dbt + SQL Server,Data Vault 核心层 + 星型模型集市层

posted on 2026-08-17 11:36  哥本哈士奇(aspnetx)  阅读(2)  评论(0)    收藏  举报

导航