---
title: D1 到 PostgreSQL 迁移指南
slug: d1-to-pg-migration
date: 2025-10-07
author: Frankie 徐
category: fullstack
tags: ['d1', 'postgresql', 'migration', 'database']
description: 详细的数据库迁移步骤，从 Cloudflare D1 迁移到 PostgreSQL 的完整指南，包括数据迁移和应用改造。
permalink: https://www.210k.cc/d1-to-pg-migration
---

更新时间：2025-09-07

本文档给出从 **Cloudflare D1 (SQLite)** 迁移数据到 **PostgreSQL** 的示例流程与脚本。

---

## 1. 总体流程

1. **导出 D1 数据**：从 Cloudflare D1 导出 CSV/NDJSON 文件。
2. **建 PG Schema**：使用兼容的 Schema（UUID/ULID 主键、BIGINT 时间戳、TEXT JSON 等）。
3. **导入 PG**：使用 `psql \copy` 或脚本批量导入。
4. **校验**：行数校验、主键唯一性、抽样哈希校验。
5. **切换应用**：更改 Drizzle ORM 数据源 → Postgres。

---

## 2. 导出 D1 数据

Cloudflare Wrangler 提供导出工具：

```bash
# 导出整个数据库为 SQLite 文件
wrangler d1 export guess_db --local --output=backup.sqlite

# 或者导出为 SQL dump
wrangler d1 export guess_db --output=backup.sql
```

如果需要 CSV：

```bash
sqlite3 backup.sqlite \
  -header -csv "SELECT * FROM users;" > users.csv
sqlite3 backup.sqlite \
  -header -csv "SELECT * FROM sessions;" > sessions.csv
```

---

## 3. PostgreSQL 建表

示例（users）：

```sql
CREATE TABLE users (
  id TEXT PRIMARY KEY,
  username TEXT NOT NULL UNIQUE,
  password_hash TEXT NOT NULL,
  is_admin INTEGER NOT NULL DEFAULT 0,
  created_at BIGINT NOT NULL,
  last_active_at BIGINT
);
```

完整表结构见 `db-compatibility.md`。

---

## 4. 导入 PG

使用 `psql \copy`：

```bash
\copy users(id,username,password_hash,is_admin,created_at,last_active_at) \
FROM 'users.csv' DELIMITER ',' CSV HEADER;

\copy sessions(id,status,host_username,started_at,ended_at) \
FROM 'sessions.csv' DELIMITER ',' CSV HEADER;
```

或用 `pgloader`（支持 SQLite → PG 直接迁移）：

```lisp
LOAD DATABASE
     FROM sqlite:///backup.sqlite
     INTO postgresql://user:pass@localhost:5432/mydb

 WITH include drop, create tables, create indexes, reset sequences;
```

---

## 5. 校验

- **行数**：确认表记录数一致。
- **约束**：检查主键、唯一索引。
- **抽样**：对比用户哈希或时间戳，确认一致。

---

## 6. 应用切换

Drizzle ORM 切换：

```ts
// D1
import { drizzle as drizzleD1 } from "drizzle-orm/d1";
const db = drizzleD1(env.DB);

// PostgreSQL
import { drizzle as drizzlePg } from "drizzle-orm/node-postgres";
import { Client } from "pg";
const pg = new Client({ connectionString: process.env.DATABASE_URL });
await pg.connect();
const db = drizzlePg(pg);
```

业务层（Repository/Service）无需修改。

---

## 7. 建议

- 小规模数据：CSV 导出/导入足够。
- 大规模数据：用 `pgloader`，能自动建表并迁移索引。
- 迁移前建议在测试库跑一遍，确认 Schema & 数据一致。

---

## 8. 总结

- 导出 → 导入 → 校验 → 切换。
- 推荐 `pgloader` 做全量迁移。
- 应用层只需改 Drizzle 数据源，业务逻辑不变。