2. 数据加载与暂存区实战
Snowflake 的数据加载体系围绕一个核心概念展开:阶段(Stage)。阶段是文件与表之间的中转层,它既可以是 Snowflake 内部管理的存储区域,也可以指向外部云存储桶。理解阶段、PUT、COPY INTO 与 Snowpipe 四者的分工,是掌握批量加载与持续加载的关键。
内部阶段与外部阶段的定位差异
内部阶段由 Snowflake 托管存储,分为三类:用户阶段(@~)、表阶段(@%表名)和命名阶段(@阶段名)。用户阶段与登录用户绑定,适合个人临时上传;表阶段与具体表绑定,生命周期随表;命名阶段是数据库对象,可授权给角色共享,是团队协作中最常用的形式。外部阶段则通过 CREATE STAGE 指向 S3、Azure Blob 或 GCS,Snowflake 只保存元数据与访问凭证,文件仍留在客户自己的存储桶中。
选择依据很直接:数据已在云存储、需要多工具共享、或希望保留原始文件归档,用外部阶段;数据需要先上传到 Snowflake 再加载、或希望简化权限管理,用内部阶段。外部阶段必须配置存储集成(Storage Integration)或访问密钥,前者通过 IAM Role 委托授权,避免在 Snowflake 中保存长期密钥,是生产环境推荐做法。
CREATE STAGE sales_ext_stage
URL = 's3://demo-bucket/sales/'
STORAGE_INTEGRATION = s3_int
FILE_FORMAT = (TYPE = CSV FIELD_OPTIONALLY_ENCLOSED_BY = '"' SKIP_HEADER = 1);
CREATE STAGE sales_named_stage
FILE_FORMAT = (TYPE = JSON);
PUT 与 COPY INTO 的批量加载链路
内部阶段的文件上传依赖 PUT 命令,它只能在 SnowSQL 或 JDBC/ODBC 客户端中执行,不能通过 Web 界面运行。PUT 会把本地文件压缩后上传,并自动计算文件的 MD5 值用于后续去重。上传完成后,用 LIST 确认文件已就位,再用 COPY INTO 把阶段中的文件加载进目标表。
PUT file:///data/sales_202401.csv @sales_named_stage AUTO_COMPRESS=TRUE;
LIST @sales_named_stage;
COPY INTO sales_raw
FROM @sales_named_stage
FILE_FORMAT = (TYPE = CSV SKIP_HEADER = 1)
ON_ERROR = 'CONTINUE'
PATTERN = '.*sales_2024.*\.csv\.gz';
COPY INTO 的关键行为是幂等去重:Snowflake 会记录每个已加载文件的名称与校验和,默认 64 天内重复执行同一 COPY 不会重复加载同一文件。这一机制让加载任务可以安全重跑。加载结果通过 COPY_HISTORY 表函数或 VALIDATE 命令查看,重点关注 rows_loaded、errors_seen 与 status 三列。若出现解析错误,可先用 COPY INTO ... VALIDATE 只做校验不落库,定位问题行后再决定 ON_ERROR 策略:CONTINUE 跳过错误行、SKIP_FILE 跳过整个文件、ABORT_STATEMENT 直接终止。
Snowpipe 实现持续加载
批量加载是"人拉"模式,Snowpipe 则是"事件推"模式。它通过云存储的事件通知(如 S3 的 ObjectCreated)触发,文件一落地就自动执行预定义的 COPY 语句。Snowpipe 使用独立的计算资源,按实际消耗计费,不占用用户仓库,因此不会与查询任务争抢资源。
CREATE PIPE sales_pipe
AUTO_INGEST = TRUE
AS COPY INTO sales_raw
FROM @sales_ext_stage
FILE_FORMAT = (TYPE = CSV SKIP_HEADER = 1);
创建后需执行 SHOW PIPES 获取 notification_channel 的 ARN,再回到 S3 控制台配置事件通知指向该 ARN。若无法使用事件通知,可退化为 REST API 调用 insertFiles 端点手动触发。Snowpipe 的加载状态通过 PIPE_USAGE_HISTORY 与 COPY_HISTORY 联合排查,常见问题是通知未送达、文件路径不匹配 PATTERN、或文件格式与定义不符。
半结构化数据的加载与查询
JSON、Avro、Parquet、ORC 等半结构化文件加载时,最省事的做法是先把整条记录装入一个 VARIANT 列,再用点号或方括号路径提取字段。这样原始结构完整保留,后续可按需展开,避免因 schema 变动反复改表。
CREATE TABLE events_raw (
raw_data VARIANT,
load_ts TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);
COPY INTO events_raw (raw_data)
FROM (SELECT $1 FROM @events_stage)
FILE_FORMAT = (TYPE = JSON);
SELECT raw_data:user_id::STRING AS user_id,
raw_data:event_type::STRING AS event_type,
raw_data:ts::TIMESTAMP_NTZ AS event_ts
FROM events_raw
WHERE raw_data:event_type::STRING = 'purchase';
若 JSON 是嵌套数组,可用 LATERAL FLATTEN 展开:SELECT f.value:item_id::STRING FROM events_raw, LATERAL FLATTEN(input => raw_data:items) f。对于字段固定的场景,可在 COPY 时用 MATCH_BY_COLUMN_NAME 直接把 Parquet 列映射到表列,省去手动提取。半结构化数据加载后建议建立物化视图或搜索优化,否则每次查询都要做路径解析,大表上开销明显。
.gz 后缀;若用 AUTO_COMPRESS=FALSE,则 PATTERN 匹配原始扩展名。两者不一致是"文件明明在阶段里却加载不到"的最常见原因。AUTO_INGEST 一旦开启,事件通知配置错误不会报错,只会静默不触发。排查时先确认 SHOW PIPES 返回的 notification_channel 与 S3 事件通知中的 ARN 完全一致,再检查桶策略是否允许该 ARN 发送事件。加载策略选择速查
| 场景 | 推荐方式 | 关键配置 |
|---|---|---|
| 本地文件一次性导入 | PUT + COPY INTO | 内部命名阶段、ON_ERROR |
| 云存储批量历史数据 | 外部阶段 + COPY INTO | Storage Integration、PATTERN |
| 文件持续到达、近实时 | 外部阶段 + Snowpipe | AUTO_INGEST、事件通知 ARN |
| JSON/Parquet 结构多变 | VARIANT 列 + 路径提取 | FLATTEN、MATCH_BY_COLUMN_NAME |
把上述链路串起来看:内部阶段解决"文件怎么进 Snowflake",外部阶段解决"文件已经在云上怎么读",COPY INTO 解决"读进来怎么落表、怎么容错、怎么去重",Snowpipe 解决"文件不断来怎么自动接"。四者组合使用,才能覆盖从一次性迁移到持续摄入的完整加载需求。