更新时间:2025-09-07
本文档总结了在使用 Cloudflare D1 进行前期开发时,如何兼顾 MySQL 与 PostgreSQL 的兼容性设计,确保后续迁移成本可控。
1. 总体思路
- Serverless 无状态 → 所有业务状态需外部化存储。
- D1(SQLite 托管):MVP/内部工具首选,无需运维。
- 迁移原则:
- Schema 设计时采用 跨库最小公约数;
- 数据访问层(Repository / DAL)收口;
- 使用 Drizzle ORM 等跨方言 Query Builder;
- 数据迁移通过导出 → 导入 → 校验 → 切换完成。
2. 数据类型对比
概念 | D1 (SQLite) | MySQL | PostgreSQL | 建议写法 |
---|---|---|---|---|
主键 | INTEGER PRIMARY KEY (rowid) / TEXT | AUTO_INCREMENT / UUID | SERIAL/BIGSERIAL / UUID | UUID/ULID (TEXT/CHAR) |
整数 | INTEGER (64bit 动态) | TINYINT/INT/BIGINT | SMALLINT/INTEGER/BIGINT | INTEGER/BIGINT |
小数 | REAL | FLOAT/DOUBLE/DECIMAL | REAL/DOUBLE/NUMERIC | NUMERIC(p,s) |
字符串 | TEXT | VARCHAR(n)/TEXT | VARCHAR(n)/TEXT | TEXT 或 VARCHAR(191) |
布尔 | 无,常用 0/1 | BOOLEAN (实际 tinyint) | BOOLEAN | INTEGER 0/1 |
日期时间 | TEXT /INTEGER (epoch) | DATETIME/TIMESTAMP | TIMESTAMP [TZ] | BIGINT 毫秒时间戳 |
JSON | TEXT + json1 扩展 | JSON | JSONB | TEXT (应用层 parse) |
二进制 | BLOB | BLOB/VARBINARY | BYTEA | BLOB/BYTEA |
3. SQL 功能差异
功能 | D1 (SQLite) | MySQL | PostgreSQL | 建议 | ||||
---|---|---|---|---|---|---|---|---|
自动增长 | INTEGER PRIMARY KEY rowid | AUTO_INCREMENT | SERIAL/BIGSERIAL | 避免,用 UUID | ||||
UPSERT | ON CONFLICT(col) DO UPDATE | ON DUPLICATE KEY UPDATE | ON CONFLICT(col) DO UPDATE | 用 ORM API | ||||
字符串拼接 | ` | ` | CONCAT() | ` | ` | ORM 封装 | ||
JSON 提取 | json_extract(text,'$.k') | JSON_EXTRACT() | -> , ->> , #>> | 应用层解析 | ||||
外键 | 支持,需 PRAGMA foreign_keys=ON | 支持 | 支持 | 显式定义 | ||||
窗口函数 | 3.25+ 支持 | 8.0+ 支持 | 原生支持 | 注意版本 |
4. Schema 设计规范
- 主键:统一
UUID/ULID (TEXT/CHAR(26))
,避免自增。 - 时间戳:统一
BIGINT
毫秒数。 - 布尔:统一
INTEGER 0/1
。 - JSON:统一
TEXT
存储,应用层 parse。 - 索引/唯一约束:显式声明,避免隐式行为。
- 避免 SQLite 专属语法(如
WITHOUT ROWID
)。
5. DDL 示例
5.1 D1 / SQLite
sql
1234567
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 INTEGER NOT NULL
);
5.2 MySQL
sql
1234567
CREATE TABLE users (
id CHAR(26) PRIMARY KEY,
username VARCHAR(191) NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
is_admin TINYINT NOT NULL DEFAULT 0,
created_at BIGINT NOT NULL
) ENGINE=InnoDB;
5.3 PostgreSQL
sql
1234567
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
);
其余表(sessions、questions、submissions 等)同理,字段规则保持一致。
6. 迁移注意事项
- UPSERT 语法:SQLite/PG 相同,MySQL 不同 → ORM 封装。
- 布尔/时间戳/JSON:建议统一为 0/1、BIGINT、TEXT,迁移最顺滑。
- 外键:SQLite 默认关闭,记得
PRAGMA foreign_keys=ON
。 - 数据迁移流程:
-
wrangler d1 export
→ CSV/NDJSON; - 导入目标库(MySQL/PG);
- 校验行数 & 唯一约束;
- 测试通过后切换 DAL 数据源。
-
7. 代码层面最佳实践
- Repository 模式:定义接口,应用层不直接写 SQL。
- Drizzle ORM:支持 D1/PG/MySQL 驱动,Schema 一份,驱动可切换。
- 示例:
ts
1234
export interface UserRepo {
create(u:{id:string; username:string; pass:string; at:number}):Promise<void>;
findByUsername(name:string):Promise<User|null>;
}
不同实现:
-
user.repo.d1.ts
→ 用 drizzle/d1。 -
user.repo.pg.ts
→ 用 drizzle/pg。 -
user.repo.mysql.ts
→ 用 drizzle/mysql。
切换时只换绑定,不动业务代码。
8. 总结
- D1:轻量、无运维,适合 MVP。
- MySQL:高并发、事务型业务。
- PostgreSQL:功能全,适合复杂查询与扩展。
- 迁移核心:提前规划 Schema(UUID/时间戳/布尔/JSON),用 ORM 屏蔽方言差异。
- 最终收益:后续切库时,应用逻辑几乎不用改,主要工作是数据迁移。