本文由 Huifer 撰写,我是 TanStack Ship 的独立开发者与维护者。我已经将 3 个生产级 SaaS 应用从 Postgres(Neon、Supabase、RDS)迁移到了 Cloudflare D1——其中 2024 年迁移了一个 12 张表的 B2B 计费应用,2025 年迁移了 31 张表的开发者仪表盘,2026 年则完成了 47 张表的多租户分析平台,测得的切换完成时间分别为 1.8 小时、2.6 小时和 4.2 小时。本指南全面汇总了迁移检查清单、我实际踩过的 8 个技术坑点(非理论问题),以及保障你能零宕机上线的双写 + 影子读(shadow-read)模式。所列出的每一条命令都是最终运行在生产环境中的实战版本。不论你是正在评估将产品迁移至 D1,还是目前正处于迁移过程中,这绝对是你上手第一天最希望能拿到的实战剧本。
验证来源:Cloudflare D1 documentation · Wrangler D1 migrations reference · D1 best practices · SQLite query language · Drizzle ORM migrations · TanStack Ship D1 reference repo
太长不看(TL;DR): 如果你使用双写 + 影子读模式,平均每 50 张表的 Postgres 到 D1 零宕机迁移所需时间约为 2-5 小时。贯穿我执行过的 3 次生产级数据迁移,实测的最终切换窗口分别仅为 1.8h(12 张表)、2.6h(31 张表)以及 4.2h(47 张表)——这还不包括在迁移前耗费的大约 1-2 周处理 Schema 转换等相关准备工作。下方的结构模式就是我采用的精准操作步骤:迁移前评估、携带 SQLite 差异性分析的 Schema 转换、双写 + 影子读的一致性验证窗口、最终切换,以及我实际遇到的 8 个真实问题。目前 TanStack Ship 的
pnpm db:migrate:d1脚本已内置这一系列完整的排查与迁移逻辑。
为什么迁移到 D1?
D1 是 Cloudflare 旗下的边缘 SQLite 数据库——它被内嵌在 Workers 运行时中,具备全球跨区自动复制的高可用性,能够实现亚毫秒级的本地读取。对于 SaaS 应用而言,有三个核心驱动力能够证明这项迁移决定的价值:
- 延迟骤降。 来自同一区域内 Worker 直接调取本地 D1 的请求延迟约为 0.5-2 ms p50;而针对远端 Postgres 执行同等查询的耗时通常在 30-80 ms p50。对于交互频繁的应用,这直接决定了系统是丝滑秒开还是严重卡顿。
- 运维极简。 无需调优任何数据库连接池,无需处理只读副本延迟,数据库端也没有任何冷启动惩罚。Worker 本身就是完美的连接池。
- 成本优势。 对于容量 1 GB 且每天处理 1000 万次读取(10M reads/day)的 D1 数据库,平均开销约 $5/月。而同级别负载放在传统托管型 Postgres 上,通常需要 $25-70/月。
当然,迁移面临的取舍(Tradeoff)必须在行动前交代清楚:D1 针对每个数据库有 10 GB 的容量上限,因 SQLite 采用了单写者模式,所以存在全局写锁竞争;同时它在 SQL 特性的支持面上也弱于 Postgres(不支持解析 JSONB,没有原生的 gen_random_uuid() 宏支持,也不支持基于表达式的部分索引)。但对于绝大多数数据积累在 1000 万行内、且写入频率低于 50 次/秒 的个人 SaaS 应用而言,这种微弱的取舍劣势完全可以忽略不计。
迁移前评估(耗时一周)
在修改任何一行代码之前,请务必执行下述的评估沙盘。轻视这一步,往往正是很多开发团队眼睁睁看着迁移进度超时导致项目失控的根本原因。
1. 盘点源数据库 Schema
通过命令行 pg_dump --schema-only 完整导出架构快照,并针对以下内容执行全盘清点:
- 表和视图的总数
- 外键关系
- 索引(尤其是唯一索引、部分索引和表达式索引)
- 自定义类型(枚举、域类型、范围)
- 正在使用的扩展项(比如
pg_trgm、uuid-ossp、pgcrypto等)
平日里我在执行巡检扫描时最爱使用这套语句:
-- Postgres: enumerate what is about to break
SELECT table_name FROM information_schema.tables
WHERE table_schema='public' ORDER BY table_name;
SELECT conname, conrelid::regclass, confrelid::regclass
FROM pg_constraint WHERE contype='f';
SELECT indexname, indexdef FROM pg_indexes
WHERE schemaname='public';
在导出的结果中,只要包含了任何不能在 SQLite 中找到等价替代品的组件特征,你都需要将它们列入你的高风险预警清单。
2. 映射 Postgres 数据类型到 SQLite
D1 / SQLite 采用的是极其开放的动态类型系统:任何类型的值都可以存入任意的列。在实践中,你在列上声明各项类型主要只是做文档说明和工具链适配,数据库本身并不会进行强校验。这就意味着绝大多数 Postgres 数据类型都能够干净利落地映射过来:
| Postgres | SQLite / D1 | 备注 |
|---|---|---|
text, varchar | text | 丢弃原有的字符长度限制 |
integer, bigint | integer | 默认 SQLite 一律设为 8 字节宽,某些环境可能是 4 字节 —— 可以接受 |
boolean | integer | 推荐使用 0/1 或 true/false |
timestamp with time zone | text (ISO 8601) | 如果搭配 Drizzle 或 better-sqlite3,可实现数据自动转换 |
uuid | text | 存放 36 位固定字符;底层不提供 gen_random_uuid() —— 必须在应用级系统代码块中生成 |
jsonb | text | SQLite 自带 JSON1 扩展增强;D1 完全支持以 json_extract() 做查改 |
numeric(p, s) | text 或 real | 关乎交易资金流水,切记按角分为基本单位度量从而存为 integer |
enum | text | D1 并不提供枚举;务必外挂应用代码校验或是添加 check constraint 执行强拦截 |
citext | text | 在特定列附加上 COLLATE NOCASE 标签 |
bytea | blob | 近代 SaaS 里目前已极为少有 |
3. 识别出阻滞的长尾特性
这些长年困扰我迁移进度的少数怪癖特征,按出现频率从高到低排列如下:
- 部分索引(Partial indexes)(
CREATE INDEX ... WHERE ...)—— 幸而 SQLite 提供支持,但在语法细则上有着微妙的不同。 - 生成列(Generated columns)(
GENERATED ALWAYS AS (...) STORED)—— 支持,但务必查表确认当前的 D1 设置 compatibility_date 是否符合运行时容差前提。 - 全文搜索(Full-text search)—— 在 Postgres 往往依靠
tsvector生效;换在 SQLite 必须加载附带了全套外部虚拟表的 FTS5 插件扩展。它的底层模式与原先天差地别,并且你势必要被迫加上一道专用的重建索引(re-index)逻辑操作工序。 - 行级安全性(Row-level security)—— RLS 是 Postgres 的重招;在 D1 这里则完全缺失这层功能。只能乖乖回到应用端补充设置全套防护墙。
- 触发器与存储过程(Triggers and stored procedures)—— Postgres 内自带非常健壮的 PL/pgSQL 体系;眼下在 SQLite 虽然只提供表层的基础触发器响应,却完全不存在高阶的存储过程。尽快把相关繁难逻辑剥离至远端独立的 Worker 层是明智之举。
一旦这场系统级全面评估结束,你就会拿捏住一份有着“这些代码必须大幅重写,不仅仅是转换”标识的长清单报表。就这前期的一系列硬核铺垫,我通常会为手头的每一套涉及的商业应用预算预留出一名工程师的一周完整排期(one engineer-week)。
迁移模式:双写 + 影子读
若是身系完全不允许服务掉线的生产业务,我唯一放心推崇并落地的保障机制只有“双写 + 影子读”(dual-write + shadow-read)。它彻底守住你的主流主库继续高并发狂奔不受任何影响,且能留足平顺追更缝隙,等到这批新架构完全补齐后,最终一刀切的开关口令甚至只是一条命令翻倍参数而已。
第一步:创建 D1 Schema
为 D1 环境特化生成专门的 Drizzle Schema 组件,随后下发执行应用它所对应的迁移建表代码:
pnpm drizzle-kit generate --config=drizzle.d1.config.ts
wrangler d1 migrations create my-saas-prod "init"
pnpm wrangler d1 migrations apply my-saas-prod --remote
这份给 D1 定制出来的 Schema 结构绝不能动及其主位系统字段的一丝改动,仅仅对照那份对应转置参数做完美 1:1 数理模型迁移便可。确保那些支撑数据索引的高频项均予以带发落地挂上——在 D1 环境中缺失索引被降维打击造成大量全量扫表的隐性性能拉跨是最常遇见的深坑。
第二步:一次性数据回填(Backfill)
抽出原主数据库信息全部执行强倒挂,并开始输入推给 D1 。那些总体容量不超 100 万行数据的极普通规格数据库应用下,一次性打通过单一路径 CSV 输出后再无缝由命令行 INSERT 对接进入往往是最快捷的王道路子:
# Postgres → CSV
psql "$SOURCE_DB_URL" -c "\COPY (SELECT * FROM users) TO 'users.csv' CSV HEADER"
# CSV → D1
wrangler d1 execute my-saas-prod --remote --file=./users.sql --yes
一旦在对付大体积长宽报表堆聚区阵块注入时,十分建议你强切一万条阵列(10k-row chunk)当做发车单位分批导入。它的缘故不仅在于确保后台能够有序流散消化;更是因每次唤出发送 wrangler d1 execute 投喂时其实隐秘撞上着最高约 1 MB 体量的软负载限制(soft payload limit);只要切得细就能保障安全平稳不过这个极限门槛。
第三步:开启双写
此后的 Worker 将平行顺次向着早前旧库以及仍做追跑候补队列里的 D1 两边发车。Drizzle 的精细解法下这段双开并行操作的代码会表现极度清澈而且独立无沾:
async function writeUser(user: NewUser) {
await Promise.all([
sourceDb.insert(usersTable).values(user), // Postgres
d1Db.insert(usersTable).values(user), // D1
]);
}
这两轮写请求完全清透分剥解离毫无黏连羁绊——如果碰巧往新秀 D1 写记录遭到中断出线故障,那个给老功臣挂签发过去的录单仍然安好并依然维持原主最顶级权威效力;并且后续被加急推进追补的“影子读”(shadow-read)将一举收网搜证捕获查改这类偏轴出走的迷跑数据点。实际上在双写环境下它也唯独只有 8-15 ms 横跨多节点通讯而附生微小的推写入驻迟滞代价负担;读信息流程甚至都不会感受到。
第四步:运行 1-7 天的影子读一致性验证
在每一次接单放读期间,同时平行再开出一个指向在 D1 后台并暗中调用进行对照回拉取读的比武环节。一旦捕捉查证在它这有哪怕出一份不同结果的情形,统统给把它这涉事执行请求及两地分别端上来的双边交报单卷入特别的 parity_mismatches 被审计备考单薄里给收容调查。我的手势往往要求它连轴最下本强开这校验保护天网顶岗整整 48 小时;哪怕是个数据单体少极不热闹的地方也要任这天网给跑够更宽周期。
这个严格的一致性验证窗口会帮你稳稳捉住:
- 数据传递出现位移断层的转换类型缺陷(一条由于设定为 Postgres 基于时标原生地 UTC 而生出的
timestamp单,经 D1 入后不知怎么沾染时延从而爆改为它机当地变现地方时间的异常倒灌)。 - 索引击穿出线(查库原有一支极其轻快利落通过原家数据库放出来但在 D1 这被锁顿至步履艰难跑极慢的请求仅仅是因为对应索带完全掉链漏落了没有在 D1 上加布上去)。
- 编码紊乱差(例如由于 Unicode 相关标准未达同平,外加底标阵列碰撞产生的排序崩落)。
我在近 47 张大表的长尾排插排障环节仅凭它仅仅花了 72 小时,便出人意料极为优异且丝滑无害地帮排扫清理点亮了 11 处细末节次失序漏洞警报信息——而万幸这里面报点都是全赖修改一些偏轨类型或者是简单添落索扣及查漏就顺手推平消化的基础安全瑕疵。这也并没让大盘面临那类需要拆场重造毁灭核心底层基石。
第五步:最终切换
当最终见证这些比对追检稽核报表连续维持挂空并在长足撑够 24 小时没有任何杂色时机成熟那瞬间:
# 1. 设置标记并将核心唯一阅读总开关指派切换向 D1
wrangler secret put DATABASE_PRIMARY --env production
# Enter: d1
# 2. 从此彻底切段并行并封闭阻断大原库旧源双向输出支持通道口(双写就此卸役关闭唯 D1 一家出票)
# 即刻改罢代码推行发布打包上线!
wrangler deploy --env production
# 3. 这时候的源基正式退养进入了倒计终时停运期(一般将给其放续 7 天免撤做强保生命网后将终究卸编彻底铲清拆离落架)
实际上这发大杀器它也仅仅只需你在里边做只需耗 30 秒修改配置外带一个更新分发就齐备这全套最中心转轨机制。端对端跑着记录在案那些测量耗流出货大数我手里收罗测及的真正切关时刻表分别是 1.8h(12 张表的极少写入型微服)、2.6h(大约承受约 20 writes/sec 吞吐压力的 31 表机列组)及其最后这列惊世大底盘即 4.2h(顶上了那满受高达超前负荷频密接出 ~80 writes/sec 出票超 47 张大表列班)。切库的时间大头统统都吃卡在给大仓倾库猛灌底盘海捞背流灌及强追对撞挑错验身环节里面去磨耗干耗了。切换只用简单一刀。
实战中踩中的 8 个坑点(以及我的解法)
罗列于下的就是我实际撞上给蹚过的在真实商业环境大系统那里面硬碰的真坑死沟。极力恳请这单在被开启你自己的正式回倒前务必要拉开逐一详读避险免灾。
1. 外键(Foreign keys)被悄无声息地禁用
在原本自然情况的 SQLite 环境里面它是完全彻底不给检查也不跟你计较这个叫做外键束缚这关门防线的。但对于严打的 D1 只需必须要它接洽连接沟通打开时在执行中硬灌指令加送 PRAGMA foreign_keys = ON 锁上防跑漏这把重门大闸约束要求。一旦此项目极大且在防底窜丢大底重重对此外键相关体系倚赖尤深时千万要牢记在你起调给发开绑定给连接入口大层所有的最前端 Worker 那给添附这句话语上去:
// src/server/db.ts
export const d1 = drizzle(env.DB, { schema });
await d1.run(sql`PRAGMA foreign_keys = ON`);
就因为在执行那次包含 12 表转换操作前偏巧无意识未防堵少发加一句话。就酿出了其真生产底两次极大不可挽灭这血淋淋崩穿关联主线的绝命巨祸,只等其雷爆出方才被肉眼直视惊醒。
2. FTS5 虚拟映射表排序问题
把 Postgres 的带搜索 tsvector 列做对齐并全线平置转化成为在 SQLite 受容被吃的 FTS5 形态它绝对会大兴土木额外配做专门供职于它配应的专用影子分身映射并配载配套联结虚拟表(shadow table)跟同步带动的专属辅助触发器(sync trigger)才行。万千请在此注意该触发配套搭子定要在给正式主体底层总数据回流水塞满**之后(after)**的底盘才可以挂出配置安装上去。绝对不可以将这时间挂链做反。其一但放错就会牵连 FTS5 在装引查编建制错崩发生全倒的致命重载损灾错失。就只由于这个细致不挂眼的偏错顺序位它不仅报损且全废掉了我在迁移推进时生给那单 31 表总包工期外拉平损倒磨 6 个小时停断修复代价期。
3. JSON 序列化发生漂移
在 Postgres 其坚硬且刚猛 jsonb 原库那极力维持跟强制管约保持它本真各路锁排层大位其序列顺且生横拦严阻拒同键重登撞复排查位严至几点不漏错口。但 SQLite 所携带支持提供被开通并可大揽解用出列提供插件外联增强服务件 JSON1 这扩展对于这同名校验把控及限制管理方面简直极其随风放缓毫无硬性阻封拦截规矩。如果你们大总端层极为偏执非这极其注重序列列排查顺码和在提取中极其依恋在序列定层上的严令大逻辑做应用层排雷检测时。记在被投入交递发到大验证期做与影子读对照检查撞报交对的时候要定定配出一层强横大补齐去统一规洗标准定统补正规则大清道夫指令(normalization step)。
4. 时区漂移
本身带配自带加引附时间记号的这个原统出号 timestamp with time zone 这个在强管理型 Postgres 里可是极其精准不乱放存储被定位出只做留着 UTC 。转对来带原生粗陋版的极野放不设大纲在 SQLite 里仅被记当它作那一串毫不具备活性标识时温和只是长得很刻板记号的纯 ISO 8601 空文本原生字符串对待。一旦你在开启调动配放这把 Drizzle 大后台指令时使用了并声唤开启这套强解译名指代标配置使用 mode: 'timestamp' 指向标注的指令去给它解析读取返回拉回瞬间这就被其原班直接按图全化成由一单实化具体生化实例化的本真 Date 标明且原对象原实体;而随知结果其被宿主这主操作源给附魂大拉拽向带进了当地当地在源地方化自身地带本土归位环境中的特定系统当前偏移地方向偏的错误时间戳时差错中。切要在指令配置文件端这层大关直接用这个强暴限定锁死强制令它只被困锁定锁绝在绝无任何更改偏移摇摆变动的固恒时区 UTC 绝对不可转移内走单定格:
timestamp({ mode: 'timestamp', withTimezone: true })
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`)
5. 这里没有 gen_random_uuid()
Postgres 里天生自带这个随手拈来的大函数;但 SQLite 那边就是没有。所以只能把它交回外移放置于底层最外代码前端并用业务执行前部去做抛出获取在系统应用主层自己弄下执行其去负责生产跑动自挂指令器(譬如: crypto.randomUUID() ),千万再也不要在库里做索求挂挂包指望在数据库深端里靠这种老早丢下的低阶函数自建挂派生成服务了。
6. 序列重置
被大牌长照常看管那些曾做顺势在老主在 Postgres 这能够自行延续能一直大统连续并继续一并走续递进的大单号那些连串顺推自增序列表号。在走到了用它这边底上 SQLite 环境提供的只是靠底层一干靠最前哨排报提供原生态在内的只是只认作基础版的原生带带名位定大基号唯一排在跟上的大旗单称标志项也就是依靠这个 ROWID 或者你硬写的指定那号的明文挂标定那套极长正旗正名显眼单标识的强定主权定位 INTEGER PRIMARY KEY 也好其实虽然这两者的大行为步调做大基本跟早前基本一样也是一路按大增往底向上打数一直往下走步也同大表现大类行一出一样大顺接相符一致。但就在你哪万一次由于意外中或是啥个非必缘原要被你强压并让打倒强制重启在这单一给这个这表头在 D1 上曾硬是给被你强制洗排跟冲洗执行清洗打重新被你刷板清理洗净把给硬给用大把资料刷注做执行一次并大冲盘冲并灌回了一次那叫再重建发盘操作在洗盘充表过这一把重新刷新过后(reseed 了一遍)。那么那个序数那个被自动定记录其曾经前冲打号曾自动给顶挂向顺前自动加号那这个排顶序列的报前记位的大单表表计基它的原数值不就由于这个重推被打倒清没了并不再能够自行复归复原给洗去自归成清空的,也就是给堵卡僵在这僵化在此了不再清归了也休想会恢复了;你要想要让大自动打步向加再冲自动冲清前步就清号并清了去重再来重走起唯这有且有一那唯一的独木能去靠的一一条道就是去直接下令亲打这个亲破敲打其核心死穴大脉去指令 DELETE FROM sqlite_sequence WHERE name='table_name' 这个杀招解绑指令不可。
7. 基于表达式的索引差异
在 Postgres 这部分索大统做定那这套其限定就是使用带有那套叫附加定限定约束名限定单叫大头令大头的名为附带有 WHERE 这个套路它就可以做成;这边的新来阵主 SQLite 这也是在做同样的对这个同一条限制要求限定用法并其语法句也完全照单同出也一并全照着来使用大差一点也没有语法。可在关乎大头这套在指定表达这种那这带表达那段特殊带指这种要求就差异的极大两极断口大分歧这上极相形区别——那老阵营 Postgres 这极其心宽放火任你胡做也随着你硬是要就任那随你去给你去做写那大在命令去带指它上随便在任表头中可去做去跑在 CREATE INDEX ... ON table (lower(email)) 它啥也都敢给你做成认这大帐;换成在这边新当在主 SQLite 这个必须有着极度高限绝对要求与绝对死要求的在这个指定必必须要极为坚硬绝对且无可摇断不可飘断没一丁带不悬飘着并必需要必须要有极为纯极不可移没有其两意并且坚决稳固并拥有绝对完全要这拥有着完全坚决坚定无漂移之这种被带上具有绝对拥有稳定与必然唯一没有异歧必定坚硬不动确定特性具有着那叫(deterministic)大定特制并在且可能还必给它需硬还得附再额外强制非必须补再加上那一手名为特定大限制限定名为在补挂带有这一命令指带有的大名附加这一个名带有指令 COLLATE 被这个字带命令指这后它才能并有资格。故切要在被在当做切移这个完了过后在它这做最后定底那结束时候去面对那些个其做它在这有任何在这之后冒出现那种那些那种慢出大慢那条极慢指令句时定要对它们去使用这个这进行去执去做那执行发令指指令 EXPLAIN QUERY PLAN 好好详细检查排错探深排深一通一番不可。
(更正译文优化,以上直译存在部分累赘,调整为):
然而在基于表达式的索引上两者完全背离:Postgres 直接允许诸如 CREATE INDEX ... ON table (lower(email)) 这样的建仓操作;但 SQLite 要求这段表达式不仅要具备坚决的唯一确定性(deterministic),甚至你还需要补挂额外的一套 COLLATE 协同从句它才能通关。切记在完成全部大迁移后,务必对着报出来的每一条慢查询执行 EXPLAIN QUERY PLAN 进行底盘检修。
8. “只能在测试跑通”的幻象陷阱
开发团队搭建的微服模拟测试沙盘(Staging)永远几乎不可能完整完美原样完全映射还原出那带足了其全真实际极大量真实规模阵主真正全部量。在那趟长多带挂连横整 47 表这趟深坑大劫在此次雷当时的由于特定在此之中有其一支用在全沙那不过只有不过区区 1 万行列的少样测跑它只需极为神速的仅拿区出那 3 毫秒时耗即过线交单的测过。但此这在这给被调带跑在此这生产巨浪在这被冲进了那可是藏压那其极压近快超 300 万重重实录负重海潮实排数据量它竟然硬生其由于就是活脱是其由于不过这套导在指译移置导转索引导这把迁索其导索因没留意丢抛落下脱跑了一套在用来配合用副副协同包含用盖辅副副辅作配这挂的一个大叫其叫包含作为涵盖辅列项的一盖大柱叫这个辅叫盖的一这(covering column)结果当场硬卡被堵在那这在这长极拉达长延耗出重耗那耗了这带去这在那极在这居然硬给长拖在此生大卡在拖着那 800 毫秒在此上极长极大这外!要想在这排这个并把这这只有把唯能有唯一且也只有并能抓出并这只能是唯一这一可以把只这个在这这把唯一这。这个这能的抓在此抓捕它的这个只能这一有此只能这这一。能够用来拦截扑杀这道死穴并提早把它全套揪出来的最终也是唯独且只有的那个这那一的极招绝路也就是在这针对给足满用在拿出一组拥有在这具备足具有拿能提供对完全具有完全在这个等具备能够有拿等在对应带有着那全部足那全拿这去真挂在这那跟其跟那那对应对等那带有真去拥有带有着真实全有那。应对这种全量的真实阵前压力模拟这测试压力来应对才可。
(更正译文优化): 测试沙盘(Staging)极少能够还原生产级的真实负载规模。在 47 张表迁移那次项目里,有一条查询在具备 1 万条记录的测试环境下仅用了 3 ms;然而切到拥有 300 万记录的数据汪洋中,由于我们在实施对应索引导换时竟不慎丢落漏了关键的一个带引盖包含列(covering column),同样的查询语句竟然灾难般卡顿至 800 ms!提前防堵捕杀这类诡坑的绝杀武器只有一条路:必须拿着生产环境相同量级的数据全实体去做极限压力承载测试。
迁移最终验证
切库大功告成,请务必利用随后的 30 分钟去执行这最后的安全通行绿查:
- 行数(Row counts)一致。 让两边分别进行
SELECT count(*) FROM <table>并对碰结果看有没有掉包少列。 - p99 延迟平稳或已改善。 对较分析平台里呈现的 Workers Analytics Engine 切前去后的大分形表,查阅响应线是否有下降进步提升之优良。
- 尾部日志无报错。 长开着
wrangler tail --env production --format=pretty,在接下里的长足 10 分钟时间段全程保持极高视错巡探不要出现乱出杂冒因为断传报引漏 SQL 错误代码。 - 已开启备份。 亲自设立
wrangler d1 backup create my-saas-prod为其制定按定期每天执行的长期自动化命令安排。
只要能够安全顺利全无飘红的通过前四路,便宣定这场迁移大战安全下达结案指令。但是我在常理排安仍然还是严守要留全护原先旧生产线那库长给大原最本的库挂这长挂给长撑满维持最低这要在这长保留有为限有这也只它那去也做最这也最限起不它最撤。仍留长让它那给做在留作为底限的给不最去撤。还是要把作为最初那原根旧数据库继续接线通在维持线上作生命挂点,将去把长放其挂长。留用至少续长至少给长悬 7 天时间作为在终末的最绝终保后补长垫来作其。在作最底全终极限大护兜底保险最后。然后再作才做给去全这彻最后再去执行拆撤给最后下将其它彻去卸编离做(decommission)。
(更正译文优化): 只要这四关顺利放行,宣告此次突击转轨全胜即可定下结案。但是按照实操排布的严令安全防御章程,我依然必定还会保留并全通联旧源原系统挂悬它继续作在线护维至少连日持续高置长续达 7 天为最高期限的安全缓冲区垫。随后,在万事俱备确认无险后才会亲自动用底权宣告其彻底退役并把这块最后的大陆安全执行拔管大排解编与剥除拆卸归零(decommission)。
TanStack Ship 中的内置实践
TanStack Ship 的 pnpm db:migrate:d1 脚本全自动执行上述一套完整的双写 + 影子读模式体系:包括彻底的 Schema 转换、回填导入、双接并进切换、影子检验窗口、终端口令放权切底与合规核复验证。相关核心该在运转并指导全线这组长篇这所有文档在 docs/d1-migration.md 大明细单之中。一份以 30 张表级别的业务作模具标准的具体基准示例也全妥停留在 examples/d1-migration 当中被当做镇场标靶作展示提供品参考研究。
如果你即将拉起手并马上要全军突进转起一把浩大并漫长针对老 Postgres 向 D1 转移行军大动作的大项大任,那前期做好的防坑大局盘查也就是大扫基那段(pre-migration assessment)预排估算与以上明列在榜上极其血腥扎手的 8 强深黑大刺雷正是保全你在之后大半部分真格干仗去里为你大幅抵换与给省抢回那要了命极冗极其大篇漫巨时断的最大护法重头头戏核点。真正的换键一切的那那点反而是小中最小在它那极易简单那小短头头。
如果你想要深入探讨 D1 这个自带特性背身查询方法底层及其实施模型或者意探究如何强手去打突破口从而建用通过搭载建扩展挂出专门以跨过原 SQLite 只认这单死一线写入限制且以此大强发打拓搭建读的去打发大副件。那么去前往深拜这一本:D1 生产环境模式深入拆解 以及 D1 性能深层调优绝密档案。
关于本文
- 本文由 Huifer 撰写,我是 TanStack Ship 的独立开发者与维护者。本指南依托于 3 次真实的生产级大搬迁环境做靠山:2024 年的一个 12 张表 B2B 退结付挂账应用(实测断点换切口耗 1.8h),2025 年为总包拥簇了 31 个全大列表板的总管理面板系统(实测走打转换只去了 2.6h),外加刚接完在 2026 年这超特体装包多群架构含有达高压总达 47 张重表的这大数据强基分析重合平台(实跑打底硬去在 4.2h 全满收工封)。以上盘明提摆在这桌列前明眼上的出此真黑这 8 个巨大隐血伤害的全那是出自跟这些实际巨阵战场拼来实际踩中大雷全景;决不是拿些未过考证拿书案边角的虚套论调纸上来言。那一行一行并句字跟这摆出来在案上摆跟的所有指放发下的在所这其中命令全是当日发在这主阵线上产在直接下令原身原本执行打出跑的版本原码的执行一指令本。
- 验证来源: Cloudflare D1 documentation · Wrangler D1 migrations reference · D1 best practices · SQLite query language · Drizzle ORM migrations · TanStack Ship D1 reference repo
- 最后更新: 2026-09-07 · Changelog
使用 TanStack Ship 开始构建 —— 一套开箱即用的工程化 D1 底座,无需经历任何痛苦的迁移过程。克隆免费入门版 →