环境与运行流程:隔离开发和生产练习
目标:让相同项目分别构建到开发与生产练习数据库,并使用失败即停止的运行脚本。
开发时你会改 SQL、注入坏数据,不应该直接影响别人正在读取的结果。环境隔离的核心是让同一套代码指向不同目标。我们用两个本地文件模拟 dev 和 prod;这不是部署到真实生产服务器。再准备一个尚未使用的 ci 目标,让下一课验证空库重建。
profile 保存连接方式,target 选择其中一组连接参数。代码里的 ref 会按目标解析对象;source 在本项目中省略 database,因此同样使用当前目标数据库,但仍指向我们声明的 main schema。
给每个环境一份数据库
完整替换 profiles.yml:
shop_dbt:
target: dev
outputs:
dev:
type: duckdb
path: shop.duckdb
schema: main
threads: 1
prod:
type: duckdb
path: shop_prod.duckdb
schema: main
threads: 1
ci:
type: duckdb
path: shop_ci.duckdb
schema: main
threads: 1
各输出的 schema 名相同,但数据库文件不同,所以对象相互隔离。仅把 target 名字改成 prod、却仍用相同 path,并不能隔离数据。本课配置没有秘密;真实数仓连接可能涉及凭据,应该通过相应环境的安全配置注入,不能照抄成本地文件后提交密钥。
预测首次查询 prod 是否已有报表。它是新的数据库,所以需要从装载开始:
uv run dbt debug --profiles-dir . --target prod
uv run dbt seed --profiles-dir . --target prod
uv run dbt build --profiles-dir . --target prod --exclude resource_type:seed
uv run dbt show --profiles-dir . --target prod --inline "select sum(revenue_cents) as cents from {{ ref('customer_summary') }}"
预期总额是 5200 分,全项目 17 项测试通过。注意每条命令都指定 prod;若 seed 到 dev 却 build 到 prod,新环境会缺原始表。
用一段脚本固定执行顺序
创建 run_pipeline.sh:
#!/usr/bin/env bash
set -euo pipefail
dbt_target="${1:-dev}"
uv sync --locked
uv run dbt debug --profiles-dir . --target "$dbt_target"
uv run dbt seed --profiles-dir . --target "$dbt_target"
uv run dbt build --profiles-dir . --target "$dbt_target" --exclude resource_type:seed
uv run dbt docs generate --profiles-dir . --target "$dbt_target"
${1:-dev} 读取第一个参数,没有提供时使用 dev;set -euo pipefail 让未定义变量、命令失败等情况停止脚本。uv sync --locked 按已生成的锁文件同步工具环境,若依赖声明与锁不一致则报错。后面依次检查连接、模拟上游装载、构建并测试、生成文档。
bash run_pipeline.sh dev
bash run_pipeline.sh prod
两次均应成功。docs 的 target 目录会保留最近一次生成的文档,若需要不同环境同时对外提供文档,应另外保存各自构建产物。本课仅本地预览,不发布。
验证隔离确实发生了
- 把 CSV 中 101 的金额改成 1300,同时把 updated_at 改成
2026-01-04 10:00:00,让已有增量表也能识别更正。 - 只运行
bash run_pipeline.sh dev。 - 分别查询两个环境:
uv run dbt show --profiles-dir . --target dev --inline "select sum(revenue_cents) as cents from {{ ref('customer_summary') }}"
uv run dbt show --profiles-dir . --target prod --inline "select sum(revenue_cents) as cents from {{ ref('customer_summary') }}"
应分别为 5300、5200。相同 CSV 在项目里变化了,但未执行 prod 装载与构建,因此 prod 仍保留上次结果。
- 把 CSV 恢复第 3 课五行原始数据。先
uv run dbt seed --profiles-dir . --target dev,再uv run dbt build --profiles-dir . --target dev --exclude resource_type:seed --full-refresh。这会重置第 12 课解释过的增量水位;prod 没有被修改。
独立练习:如果脚本中的 build 失败,docs generate 是否应该执行?只执行一次成功的脚本,明天是否会自动重跑?
参考答案
不会继续生成文档,因为脚本在失败命令处退出。可以在开发 CSV 中暂时把 placed 写成 complete,运行脚本,确认日志停在 build 失败处,再恢复输入并重新运行。失败前成功执行的任务并不会自动回滚。
脚本没有定时功能。定时器或任务编排系统可以启动它,但需要另外明确运行环境、上游装载完成条件、退出状态和通知方式。本课程没有替你建立定时任务。真实生产流程通常由独立采集系统装载 source,不会每次把交易数据当 seed 重装。
若两个环境一起变化,先查是否误用了同一个 path;若 prod 找不到 source,检查装载命令的 target;若 locked 同步失败,检查是否修改过 pyproject.toml 却没有同步锁文件。不要在自动运行时默默升级依赖。
保留三目标 profile 与脚本,ci 尚未运行。需要提交的输入包括 models、seeds、tests、macros、项目配置、依赖声明与锁文件;.venv/、target/、logs/ 和数据库文件属于环境或生成结果。这里的本地无凭据 profile 可以作为教学配置;真实团队应按凭据管理规则处理。