首页/新闻资讯/正文详情

9个步骤带你了解PHP如何安全地迁移数据库或字段

发布时间:2026/9/25 23:16:06 来源:云帆数科 栏目:资讯中心
9个步骤带你了解PHP如何安全地迁移数据库或字段
问题描述这篇文章要讲的是一个非常具体且棘手的问题唯一 ID 迁移。现在有一个实体User由User::$id标识看起来像这样123456finalclassUser{publicfunction__construct(publicint$id,) {}}访问它的数据的方式是通过一个名为UserRepository的仓储接口。这里提供一个简单的 SQLite 实现123456789101112131415161718192021222324interfaceUserRepository{/*** throws UserNotFoundException*/publicfunctionfindById(int$id): User;}finalclassSqliteUserRepositoryimplementsUserRepository{publicfunctionfindById(int$id): User {$sql...;$stmt$this-pdo-prepare($sql);$stmt-execute([id$id,]);// ...}}非常简单的设置。现在假设你的团队决定出于安全原因用户的唯一标识符不应再是整数而应该采用 UUID。当然不允许停机。解决方案为了绝对安全将分三个阶段实施让两个 ID 共存实战测试 UUID 实现淘汰以前的整数 ID 实现分阶段进行的主要原因是测试不足以确保一切都按预期工作。这个字段可能被其他作业通过 API 使用或者我现在甚至无法想象的东西。所以以防万一我希望能够在任何时刻安全地回滚到以前的实现。假设这里描述的每一步都有适当的测试覆盖理想情况下是在重构发生之前。步骤 1 - 从原始类型解耦无论你接下来做什么没有这个都不容易User类中的那个int原始类型就是在要求爆炸发生。如果你想从整数平滑过渡到 UUID最好的办法是首先将代码与原始类型解耦。一种方法是封装你的原始类型。我将创建一个名为UserId的类让代码依赖它而不是int12345678910111213141516171819202122232425262728finalclassUserId{publicfunction__construct(publicint$id,) {}publicfunctiongetId(): int{return$this-id;}}finalclassUser{publicfunction__construct(publicUserId$id,) {}}interfaceUserRepository{/*** throws UserNotFoundException*/publicfunctionfindById(UserId$id): User;}上面的代码应该会使重构稍微容易一些。当调用getId()时UserId仍然返回int但这没关系最重要的是我们的代码依赖于UserId——一个我们控制的类型——而不是原始整数——我们根本无法控制它。现在只需覆盖所有现有代码以使用UserId而不是int $id。1234567891011121314finalclassSqliteUserRepositoryimplementsUserRepository{publicfunctionfindById(UserId$id): User {$sql...;$stmt$this-pdo-prepare($sql);$stmt-execute([id$id-getId(),]);// ...}}到这里什么都没有改变。感觉可以安全地合并和部署不应该有任何东西会崩溃。顺便说一句测试帮助很大。一定要进行测试步骤 2 - 让两个字段共存现在确保可以向users表添加一个新字段。这样就可以了1sqliteALTERTABLEusersADDuuidVARCHAR;现在它既不能是NOT NULL也不能是UNIQUE因为每个现有记录的值都将是NULL。回到UserId类确保它现在的实现中有uuid1234567891011121314151617finalclassUserId{publicfunction__construct(publicint$id,public?UuidInterface$uuid,) {}publicfunctiongetId(): int{return$this-id;}publicfunctiongetUuid(): ?UUidInterface{return$this-uuid;}}它仍然是可空的因为嗯它在数据库中是 null现在需要确保发生两件事每个现有记录都将有一个非空的 uuid并且每个新记录都将已经带有填充的 uuid当看到在任何给定时刻users.uuid永远不会是NULL时则认为两者在数据库层都很好地共存。步骤 3 - 确保每个新记录都有 UUID在你的系统中某个地方存储着 Users。需要确保在它发生的任何地方UUID 字段都将被填充。所以给定这个旧的实现12345publicfunctioninsert(User$user): void {// insert into ...}只需用 UUID 生成来修补它应该就没问题了1234567891011publicfunctioninsert(User$user): void {$id$user-id;if($id-uuid null) {$id-uuid Uuid::uuid4();}// insert into ...}个人强烈建议你用测试覆盖这个 IF 语句以防你遗漏了导入或类似的东西。除此之外不应该引入其他回归。每个新记录现在应该都有正确填充的users.uuid。步骤 4 - 为旧记录回填 UUID 字段这可以用脚本完成。如果你使用迁移框架可能也会非常简单。现在只需要获取所有 uuid 为 null 的用户并填充它们。类似这样就可以完成12345$users getUsersWithEmptyUuid();foreach($usersas$user) {$user-id-uuid Uuid::uuid4();updateUser($user);}上面的代码并不能代表每个代码库但我想你明白了。步骤 5 - 确保一切正常运行不要急于切换实现。一定要确保系统正常运行并且在再运行系统几个小时后users.uuid不会是NULL。只有当你 100% 确定users.uuid在此表中永远不会是NULL时才进入下一步。步骤 6 - 更新 UserRepository 以使用 UUID看来现在已经可以切换到新的 UUID 实现了。但不建议盲目地切换到新实现。谨慎总比后悔好对吧首先确保用功能开关保护代码。用以下内容更新SqliteUserRepository1234567891011121314151617181920212223242526finalclassSqliteUserRepositoryimplementsUserRepository{publicfunctionfindById(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 实现这里是应该完成此操作的代码片段1sqliteCREATEUNIQUEINDEXusers_uuid_uqONusers(uuid);理想情况下你还应该使users.uuid为NOT NULL但我跳过了它因为它需要更多的 SQLite 步骤这些步骤与我想在这里演示的内容无关。好了现在应该没问题了。将你的更改传播到生产环境看看功能开关的代码现在表现如何。一切都好对吧是时候清理了。步骤 9 - 清理你的数字 ID既然东西已经部署并经过实战测试是时候清理以前的数字 id 字段了。无论你是删除实际字段还是只是不在代码中使用它这都是项目决策——什么不是呢但最终你的SqliteUserRepository会看起来像这样1234567891011121314finalclassSqliteUserRepositoryimplementsUserRepository{publicfunctionfindById(UserId$id): User {$sql...;$stmt$this-pdo-prepare($sql);$stmt-execute([uuid (string)$id-getUuid(),]);// ...}}插入记录的函数现在也值得一些关爱。让我们删除以前的 IF 语句1234567891011...publicfunctioninsert(User$user): void {$user-id-uuid Uuid::uuid4();// insert logic}...如果你决定也从数据库中删除数字 id必须确保UserId代码也被清理并删除$id属性1234567891011finalclassUserId{publicfunction__construct(publicUuidInterface$uuid,) {}publicfunctiongetUuid(): UuidInterface {return$this-uuid;}}因为现在 UUID 完全没有理由为空也从$uuid属性中删除了问号。现在你的系统是安全的总结当然事情可能因项目而异但归根结底你将执行所描述技术的某种变体。这适用于几乎任何依赖数据的实现更改。只需记住三个阶段让两个实现共存实战测试新实现淘汰以前的实现不要害羞或羞于采取多个步骤。即使你知道之后必须删除代码实际上回滚部署或修复实时数据库比在这里描述的任何步骤都要痛苦得多。

相关推荐

2026年锥套皮带轮制造厂家实力参考:从用料扎实到加工精度全解析
2026年锥套皮带轮制造厂家实力参考:从用料扎实到加工精度全解析

做配套报关的锥套皮带轮厂家找哪家? 我想买高牌号铸铁材质的锥套皮带轮推荐厂家? 我需要小批量锥套皮带轮样品推荐生产厂家?做配套报关的锥套皮带轮厂家找哪家?国内做锥套皮带轮出口的厂家不在少数,但是能稳定满足外贸项目验收标准,还能配套提供全套… · 2026/9/25 23:15:53

Windows 上安装 MinIO 完整指南:从部署到性能调优与避坑
Windows 上安装 MinIO 完整指南:从部署到性能调优与避坑

简介:MinIO是一款兼容Amazon S3 API的开源对象存储服务器,以高性能、可扩展和多租户支持见长,这份Windows安装包为需要在个人电脑、服务器或开发测试环境中快速搭建对象存储的开发者、运维及数据管理人员提供了便捷入口,可用于本地… · 2026/9/25 23:15:46

AI驱动创新怎么落地?企业数智化转型的完整实施路径与避坑指南
AI驱动创新怎么落地?企业数智化转型的完整实施路径与避坑指南

简介:科易网AI企业创新服务方案深度解读,面向数字化转型中的企业决策者、技术管理者及科技创新服务从业者,聚焦科技信息碎片化、技术资源匹配难、客户响应慢、人才培养周期长等痛点,系统阐述AI技术图谱、AI技术情报、AI科技报告等… · 2026/9/25 23:15:40

nginx-1.25.2已编译包部署避坑指南:从依赖检查到平滑升级
nginx-1.25.2已编译包部署避坑指南:从依赖检查到平滑升级

简介:本资源为 Linux 环境下已编译完成的 Nginx 1.25.2 版本安装包,面向需要在服务器上快速部署 Web 服务或反向代理的运维与后端开发人员,解压后即可直接运行,省去源码编译环节。压缩包共 569 个文件,约 4.26MB&#… · 2026/9/25 23:53:24

RAR for Linux 原生命令行工具深度指南
RAR for Linux 原生命令行工具深度指南

简介:本资源是Linux平台专用的64位RAR命令行工具v6.1.b1测试版,面向Linux系统管理员、运维工程师及需要处理RAR格式文件的开发者,解决在Ubuntu、Fedora等主流发行版中缺乏原生RAR支持的问题。压缩包共11个文件,含核心可执行文件&a… · 2026/9/25 23:53:24

仿抖音上下滑动切换视频:手势、滚动容器与播放器生命周期全链路
仿抖音上下滑动切换视频:手势、滚动容器与播放器生命周期全链路

简介:这是一份面向Android开发者的「仿抖音上下滑动切换视频」完整工程源码,适合具备一定Android基础、希望掌握短视频列表交互与播放器集成的中高级开发者。资源围绕RecyclerView、SnapHelper与自定义LayoutManager三大核心组件展开,解决视频… · 2026/9/25 23:53:18

仿抖音上下滑动切换视频:手势冲突与播放器复用实战
仿抖音上下滑动切换视频:手势冲突与播放器复用实战

简介:这是一份面向Android开发者的「仿抖音上下滑动切换视频」完整工程源码,适合已掌握RecyclerView基础、希望进阶学习短视频交互实现的中级开发者。资源围绕RecyclerView、SnapHelper与自定义LayoutManager三大核心组件展开,解决视频列表整… · 2026/9/25 23:53:18

仿抖音上下滑动切换视频:手势识别、预加载与播放器生命周期完整实现
仿抖音上下滑动切换视频:手势识别、预加载与播放器生命周期完整实现

简介:这是一份面向Android开发者的「仿抖音上下滑动切换视频」完整工程源码,适合具备一定Android基础、希望深入理解短视频列表交互实现原理的中高级开发者。资源围绕RecyclerView、SnapHelper与自定义LayoutManager三大核心组件展开,涵盖视频… · 2026/9/25 23:53:11

WinForms多选下拉控件:继承ComboBox并自绘复选框
WinForms多选下拉控件:继承ComboBox并自绘复选框

简介:一套面向 Windows 桌面开发者的自定义控件源码,实现将复选框(Checkbox)整合进组合框(Combobox)的“CheckComboBox”类;它解决了普通下拉列表只能单选、无法直观显示多选状态的问题&#xf… · 2026/9/25 23:52:45

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码