9 个步骤教你如何安全地迁移数据库或字段

news/2025/11/22 10:07:02/文章来源:https://www.cnblogs.com/catchadmin/p/19255602

9 个步骤教你如何安全地迁移数据库或字段

问题描述

这篇文章要讲的是一个非常具体且棘手的问题:唯一 ID 迁移

现在有一个实体 User,由 User::$id 标识,看起来像这样:

final class User
{public function __construct(public int $id,) {}
}

访问它的数据的方式是通过一个名为 UserRepository 的仓储接口。这里提供一个简单的 SQLite 实现:

interface UserRepository
{/*** @throws  UserNotFoundException*/public function findById(int $id): User;
}final class SqliteUserRepositoryimplements UserRepository
{public function findById(int $id): User {$sql = "...";$stmt = $this->pdo->prepare($sql);$stmt->execute(['id' => $id,]);// ...}
}

非常简单的设置。

现在假设你的团队决定,出于安全原因,用户的唯一标识符不应再是整数,而应该采用 UUID。

当然,不允许停机。
原文链接 9 个步骤教你如何安全地迁移数据库或字段

解决方案

为了绝对安全,将分三个阶段实施:

  1. 让两个 ID 共存
  2. 实战测试 UUID 实现
  3. 淘汰以前的整数 ID 实现

分阶段进行的主要原因是,测试不足以确保一切都按预期工作。这个字段可能被其他作业通过 API 使用,或者我现在甚至无法想象的东西。

所以以防万一,我希望能够在任何时刻安全地回滚到以前的实现。

假设这里描述的每一步都有适当的测试覆盖,理想情况下是在重构发生之前。

步骤 1 - 从原始类型解耦

无论你接下来做什么,没有这个都不容易!User 类中的那个 int 原始类型就是在要求爆炸发生。

如果你想从整数平滑过渡到 UUID,最好的办法是首先将代码与原始类型解耦。一种方法是封装你的原始类型。我将创建一个名为 UserId 的类,让代码依赖它而不是 int

final class UserId
{public function __construct(public int $id,) {}public function getId(): int{return $this->id;}
}final class User
{public function __construct(public UserId $id,) {}
}interface UserRepository
{/*** @throws  UserNotFoundException*/public function findById(UserId $id): User;
}

上面的代码应该会使重构稍微容易一些。当调用 getId() 时,UserId 仍然返回 int,但这没关系!最重要的是,我们的代码依赖于 UserId——一个我们控制的类型——而不是原始整数——我们根本无法控制它。

现在只需覆盖所有现有代码以使用 UserId 而不是 int $id

final class SqliteUserRepositoryimplements UserRepository
{public function findById(UserId $id): User {$sql = "...";$stmt = $this->pdo->prepare($sql);$stmt->execute(['id' => $id->getId(),]);// ...}
}

到这里,什么都没有改变。感觉可以安全地合并和部署,不应该有任何东西会崩溃。顺便说一句,测试帮助很大。一定要进行测试!

步骤 2 - 让两个字段共存

现在确保可以向 users 表添加一个新字段。这样就可以了:

sqlite> ALTER TABLE `users` ADD `uuid` VARCHAR;

现在它既不能是 NOT NULL 也不能是 UNIQUE,因为每个现有记录的值都将是 NULL

回到 UserId 类,确保它现在的实现中有 uuid

final class UserId
{public function __construct(public int $id,public ?UuidInterface $uuid,) {}public function getId(): int{return $this->id;}public function getUuid(): ?UUidInterface{return $this->uuid;}
}

它仍然是可空的,因为,嗯,它在数据库中是 null!

现在需要确保发生两件事:

  1. 每个现有记录都将有一个非空的 uuid;并且
  2. 每个新记录都将已经带有填充的 uuid

当看到在任何给定时刻,users.uuid 永远不会是 NULL 时,则认为两者在数据库层都很好地共存。

步骤 3 - 确保每个新记录都有 UUID

在你的系统中,某个地方存储着 Users。需要确保在它发生的任何地方,UUID 字段都将被填充。

所以给定这个旧的实现:


public function insert(User $user
): void {// insert into ...
}

只需用 UUID 生成来修补它,应该就没问题了:

public function insert(User $user
): void {$id = $user->id;if ($id->uuid === null) {$id->uuid = Uuid::uuid4();}// insert into ...
}

个人强烈建议你用测试覆盖这个 IF 语句,以防你遗漏了导入或类似的东西。除此之外,不应该引入其他回归。

每个新记录现在应该都有正确填充的 users.uuid

步骤 4 - 为旧记录回填 UUID 字段

这可以用脚本完成。如果你使用迁移框架,可能也会非常简单。

现在只需要获取所有 uuid 为 null 的用户并填充它们。类似这样就可以完成:

$users = getUsersWithEmptyUuid();
foreach ($users as $user) {$user->id->uuid = Uuid::uuid4();updateUser($user);
}

上面的代码并不能代表每个代码库,但我想你明白了。

步骤 5 - 确保一切正常运行

不要急于切换实现。一定要确保系统正常运行,并且在再运行系统几个小时后,users.uuid 不会是 NULL

只有当你 100% 确定 users.uuid 在此表中永远不会是 NULL 时,才进入下一步。

步骤 6 - 更新 UserRepository 以使用 UUID

看来现在已经可以切换到新的 UUID 实现了。但不建议盲目地切换到新实现。

谨慎总比后悔好,对吧?首先确保用功能开关保护代码。用以下内容更新 SqliteUserRepository

final class SqliteUserRepositoryimplements UserRepository
{public function findById(UserId $id): User{if (isFeatureFlagActive('enableNewUsersUuidImplementation')) {// 新实现,使用 Uuid$sql = "...";$stmt = $this->pdo->prepare($sql);$stmt->execute(['uuid' => (string) $id->getUuid(),]);// ...} else {// 旧实现,使用整数 $id$sql = "...";$stmt = $this->pdo->prepare($sql);$stmt->execute(['id' => $id->getId(),]);// ...}}
}

长话短说:如果请求的功能已启用,isFeatureFlagActive() 返回 TRUE,否则返回 FALSE。它可以基于配置、数据库条目或环境变量。这在这里不相关。

重要的是,你可以更改 isFeatureFlagActive() 的返回值,而无需重新部署代码。这样你就可以安全地回滚到以前的实现,没有太多摩擦。

步骤 7 - 部署、启用和监控

首先部署它,确保 isFeatureFlagActive() 始终返回 FALSE,这样就会选择原始实现。

然后将 isFeatureFlagActive() 切换为返回 TRUE,这样就会选择新实现——同样,这可以通过数据库记录、环境变量、SaaS 工具或你喜欢的任何东西来完成。

哦不!出问题了!网站突然变得超级慢!!

关闭你的功能开关,这样 isFeatureFlagActive() 将再次返回 FALSE

...

事情似乎又恢复正常了。回到你的 IDE,试着弄清楚发生了什么。也许做一些点击测试和调试来理解是什么导致它如此缓慢。

最终你会意识到你没有索引 users.uuid 列,所以由于你的巨大表,查询它变得超级慢。尽快修复它!

步骤 8 - 使 UUID 唯一并建立索引

由于使用的是 SQLite 实现,这里是应该完成此操作的代码片段:

sqlite> CREATE UNIQUE INDEX `users_uuid_uq` ON `users`(`uuid`);

理想情况下,你还应该使 users.uuidNOT NULL,但我跳过了它,因为它需要更多的 SQLite 步骤,这些步骤与我想在这里演示的内容无关。

好了,现在应该没问题了。将你的更改传播到生产环境,看看功能开关的代码现在表现如何。

一切都好,对吧?是时候清理了。

步骤 9 - 清理你的数字 ID

既然东西已经部署并经过实战测试,是时候清理以前的数字 id 字段了。

无论你是删除实际字段还是只是不在代码中使用它,这都是项目决策——什么不是呢?

但最终你的 SqliteUserRepository 会看起来像这样:

final class SqliteUserRepositoryimplements UserRepository
{public function findById(UserId $id): User {$sql = "...";$stmt = $this->pdo->prepare($sql);$stmt->execute(['uuid' => (string) $id->getUuid(),]);// ...}
}

插入记录的函数现在也值得一些关爱。让我们删除以前的 IF 语句:

...public function insert(User $user
): void {$user->id->uuid = Uuid::uuid4();// insert logic
}...

如果你决定也从数据库中删除数字 id,必须确保 UserId 代码也被清理,并删除 $id 属性:

final class UserId
{public function __construct(public UuidInterface $uuid,) {}public function getUuid(): UuidInterface {return $this->uuid;}
}

因为现在 UUID 完全没有理由为空,也从 $uuid 属性中删除了问号。现在你的系统是安全的!

总结

当然,事情可能因项目而异,但归根结底,你将执行所描述技术的某种变体。

这适用于几乎任何依赖数据的实现更改。只需记住三个阶段:

  1. 让两个实现共存
  2. 实战测试新实现
  3. 淘汰以前的实现

不要害羞或羞于采取多个步骤。即使你知道之后必须删除代码!实际上回滚部署或修复实时数据库比在这里描述的任何步骤都要痛苦得多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/972828.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

衡水市一对一家教机构推荐,2026最新辅导机构口碑排行榜

在衡水市桃城区、冀州区、枣强县、武邑县、深州市、武强县、饶阳县、安平县、故城县、景县、阜城县,从衡水百货大楼商圈、怡然城商圈到爱琴海购物公园、汇中广场的家长社群里,“孩子成绩提不上来”的焦虑从未消散。数…

黔西南布依族苗族自治州一对一家教机构推荐,2025最新教育机构权威测评榜单

“给孩子找一对一家教机构比跑遍黔西南的山山水水还难!”这是兴义市家长陈女士的真切感慨。她的孩子初三面临中考,数学和物理成绩一直拖后腿,两个月内试了4家课外辅导机构,要么老师不熟悉黔西南中考考点,要么课后…

铜仁市一对一家教机构推荐,2025最新教育机构靠谱测评榜单

铜仁市不少家长为孩子找一对一课外补习时总陷入两难:想选口碑好的家教辅导机构,却被广告里 “提分神速” 的噱头误导;要么遇到师资信息模糊、教学水平没保障的培训平台,要么碰上收费混乱、退费困难的教育机构。据本…

承德市一对一家教机构推荐,2026最新辅导机构权威测评榜单

“孩子数学偏科拖后腿、英语成绩常年在及格线徘徊,补了大半年物理依旧没起色,想找一家真正懂承德考情、能稳提分的一对一辅导机构,咋就这么难?”这声无奈的感叹,道出了万千承德市家长的心声。无论是双桥区南营子大…

2025年11月工业CT厂家推荐榜:权威评测与综合对比分析

作为工业制造领域的关键检测技术,工业CT在产品质量控制、缺陷分析和逆向工程中扮演着重要角色。根据2025年行业白皮书显示,随着新能源汽车、航空航天等高端制造业的快速发展,工业CT市场需求呈现稳定增长态势。许多用…

2025年二手淀粉加工设备定制厂家权威推荐榜单:二手小型淀粉设备/二手红薯淀粉加工设备/二手淀粉设备源头厂家精选

在资源循环利用与降本增效的双重驱动下,二手淀粉加工设备市场正迎来新一轮增长机遇。据行业数据显示,2024年全球淀粉市场销售额达到了23.36亿美元,预计到2031年将保持稳定增长。在这一市场中,二手设备交易规模年均…

2025年11月工业CT厂家评测榜单:结合政策导向与市场反馈的客观分析

工业CT作为无损检测领域的核心设备,其选择关系到科研精度与生产效率。本文基于行业白皮书数据与用户调研,针对工程师、质检人员、研发团队等典型用户群体,分析其在设备选型中的核心需求:例如航空航天领域注重微米级…

2025年新疆高三复读班权威推荐榜单:高三补习班/高三复读全日制/私立高中学校精选

新疆高三复读教育市场近年来呈现专业化、规范化的发展趋势,为学子们提供了多元化的提升路径。 随着高考竞争日趋激烈,新疆高三复读市场持续增长。据教育行业统计,新疆地区专业高三复读机构中,实施小班化教学的占比…

Json C语言嵌套遍历Json节点

Json//程序用到了网上比较流行的Cjson开源库,可以在网上搜索下载,有两个文件(cjson.h和cjson.c),放到目录下即可// JsonTest.cpp : Defines the entry point for the console application. // XGZ.SZ.20251122 //…

Java企业级Function Calling落地:JBoltAI的架构设计与实践之道

Java企业级Function Calling落地:JBoltAI的架构设计与实践之道在AI原生应用开发浪潮中,Function Calling(函数调用)已成为连接大模型与业务系统的核心桥梁——它让AI能够精准调用现有系统接口、执行具体业务操作,…

AI知识库检索的精度与召回平衡之道:JBoltAI的技术实践

AI知识库检索的精度与召回平衡之道:JBoltAI的技术实践在AI应用开发中,知识库检索是决定系统实用性的核心环节。单一向量相似度搜索常陷入“语义泛化则精度不足,精确匹配则召回率偏低”的困境,复杂业务场景下更是难…

AI原生应用:Java架构师的下一站,不是打补丁,是范式革新

AI原生应用:Java架构师的下一站,不是打补丁,是范式革新作为深耕企业级应用的Java架构师,我们大概率都做过这样的操作:在Service层注入RestTemplate或WebClient,调用OpenAI、文心一言的API,将返回文本展示在界面…

邢台市一对一家教机构推荐,2025最新教育机构权威测评榜单

“孩子数学几何越补越懵,英语阅读提分无果,找个懂邢台考情的一对一家教机构咋这么难?” 这声感叹,道出了无数邢台家长的心声。无论是桥东区天一广场商圈、桥西区北国商城商圈、万达商圈、中北世纪城商圈周边的核心…

AI开发别再“大材小用”:JBoltAI的分流策略让效率与成本双向最优

AI开发别再“大材小用”:JBoltAI的分流策略让效率与成本双向最优在AI应用落地的浪潮中,很多开发团队都陷入了一个隐形误区:让大模型承担了大量本可通过规则或代码高效解决的“体力活”。比如用多模态大模型完整处理…

毕节市一对一家教机构推荐,2025最新教育机构权威测评榜单

“孩子偏科补了半载仍无起色,想找家真正靠谱的一对一辅导机构,为何成了毕节家长的难心事?”这声无奈感叹,道出万千毕节家长心声。无论是七星关区、黔西市的主城区家庭,大方县、金沙县的城郊父母,还是织金县、纳雍…

1v1视频源码,js实现滚动到某个位置动画 - 云豹科技

1v1视频源码,js实现滚动到某个位置动画场景大概是点击tab可以滚动到对应的位置我在vue项目里使用如下:<ul><li @click="changeTab(first)">我是tab一</li><li @click="changeTa…

2025年新疆高三复读班权威推荐榜单:高三集训班/高三补习班/民办高中管理学校精选

新疆高三复读教育市场经过多年发展,已形成多元化、分层化的专业服务体系,为学子们提供了多种提升路径。 近年来,新疆高三复读市场呈现出专业化、规范化的良好发展态势。随着高考竞争日趋激烈,家长和学生对高质量复…

2025年KCA订做厂家权威推荐榜单:KCB/KX型/EX型源头厂家精选

在高端装备制造与特种材料需求持续增长的推动下,KCA(高性能合金材料)作为航空航天、新能源、船舶制造等领域的关键材料,其定制化市场需求显著提升。据行业数据显示,2024年中国高性能合金市场规模已达320亿元,年均…

【程序员日志】想再试试,再追一下梦想

【程序员日志】想再试试,再追一下梦想重新出发,在程序员这个岗位上再试试。荒废了半年的“武功”,不知道应聘结果如何。工作了3年,和我想象的大相径庭,离职的时候,工作压力、技术焦虑、家庭压力确实是把我压得喘…

2025宜春市一对一辅导测评排行榜:这些机构值得推荐

在宜春市袁州区、丰城市、樟树市、高安市、奉新县、万载县、上高县、宜丰县、靖安县、铜鼓县的中小学家长圈里,“找对一对一辅导”早已成为共同的焦虑。袁州的家长发愁初中物理找不到精准补漏的老师,丰城的高中生家长…