TL; DR在一个非常差的结构化数据库(具有多次重复列,没有相互关系和重复数据)之间迁移大量数据的最佳方法是什么?另一个高度组织和关系结构? - 很抱歉长篇大论!
我最近接受了一项非常复杂的工作。它改写了整个公司基于网络的IT平台。我担心我不能提供太多细节,因为我们不能让老开发者知道(他有一个反对公司负责人的隐喻枪,因为他是唯一一个知道如何做发票生成等关键事情的人,并要求越来越多的钱。)
主要问题是整个网络平台(由所有员工和所有客户使用)由一个技能低于业余的人编码。它由大约300个单独的代码文件组成。没有模板库 - 它全部硬编码到每个文件中。没有逻辑数据库结构 - 它实际上是随着他的进展而组成的。没有安全感 - 令人震惊。无论如何,我们将在约3个月的时间内重写整个平台。
然而老板说,早上它上线,任何地方都不会丢失任何客户数据。必须直接复制整个数据库内容。数据库的结构目前很差,几乎不可能使用,但本周我们将(试图!)编写一些脚本,将其迁移到我们新的,高度关系的结构,这更符合逻辑。 问题是,最好的方法是什么?
一个例子是地址。在旧数据库中,地址用于大约12个表中(总共44个表...)。在我们看来,我们有一个addresses
表,它将被其他表(例如address_id
)交叉引用以保持清洁。主要问题是,在大约一半的表中,地址存储为line1
,line2
,town
,city
等,这很好,但在另一半他只有一个address
字段存储整个事物!
第二个例子是日期 - 在某些表格中,他有几秒钟 - 自Epoch日期,在其他MySQL NOW()
日期,而在其他表格中,他确实将其存储在每行6列中 - year
, month
,day
,hour
,minute
,second
- 哎哟......
试图解决这个问题的好方法是什么? 我们应该查看我们的表并找出我们需要将他的数据从哪里拉到我们的表格中,或者我们应该反过来查看他的< / em>表格并确定他的数据需要进入我们的数据?
从编程的角度来看,我们应该如何解决这个问题?很多数据都需要动态格式化(例如日期),所以我们考虑一次一行地采集数据,格式化正确,然后将其重新插入我们的脚本中的正确位置。
查询的速度和效率对我们来说不是问题,因为我们只需要在本地计算机上运行一次(在测试之后)。当SQL转储时,他的数据库目前大约是800MB,但是这很多都是他无用的测试数据,或者说完全没必要。
有关解决此问题的最佳方法的任何想法?作为参考,我们的系统将用PHP重写,因此任何基于PHP的建议都会很好。该数据库目前(现在仍将是)在MySQL中。
答案 0 :(得分:10)
这里没有解决方案。没有魔法。只是努力工作。
你有新的模型,完成这项工作的唯一方法是转到每个表并将它们单独地,逻辑地,纸上,白板上等转换成新模型。
您需要处理的不仅仅是简单的格式问题。您还将处理数据重复问题。如果您有12个包含地址的表,但只有1个客户端,哪个地址获胜?
仅此决定可以简化大量处理(例如,您可以忽略其他地址,而不是从主客户端记录链接的一个有福地址)。
这会带给你最后的问题。转换期间“不丢失任何数据”。
根据“不丢失任何数据”的含义,这很可能是从第一天开始的。例如,如果您丢弃地址,那么就会丢失数据。当然,每个组件“都有一个地址”,但不一定是他们之前的那个。在他们可能完全相同之前,他们可能也没有。它会非常混乱。
完成映射和其他过程后,对大多数语言编码都很简单。脚本语言适用于此。您可以“按原样”将每个表批量加载到新数据库中,并编写存储过程以进行转换。无论你熟悉什么。您的转换可能会有几个步骤,而且大部分代码可能只是为了促进转换而“一次性”。
这将是乏味的。这些东西总是如此。有太多的细节。这是一个可怕的系统的所有原因是转换将是可怕的原因。如果你没有足够的时间把它拉下来,也不要感到惊讶。
最后,如果您有大量数据,如果您无法在业务停机期间(周末,过夜等)执行切换,您可能会遇到一些时间限制。如果您在运行时使用更新数据,这将是另一个鱼。如果可能的话,我强烈建议不要那样做。
答案 1 :(得分:6)
我最近做了几次大规模的迁移,并在此期间逐渐为自己开发了一些实用的最佳实践。这没什么真正开创性的,但你可能会发现其中一些有用:
一般提示
<强>移植强>
处理数据迁移的代码将在一段时间内成为项目的一部分,因此将其专用于包/文件夹(即legacy
)是个好主意。在此程序包中,保留转换脚本和与旧系统相关的其他文件。过了一段时间,你将能够通过简单的rm -rf legacy
摆脱它。
脚本应该以小步骤进行转换。最好是多次遍历一个表并保持步骤小,简单和可调试,而不是让一个大脚本尽可能快地执行所有操作。
在自己的事务中运行每个步骤并在成功完成后提交它也是一个好主意,这样当一个步骤失败时,您不需要再次重新运行整个迁移。
整个迁移过程以及特定步骤或步骤组应该可以使用命令行中的一个命令运行,因为您将多次运行它直到达到最终版本,因此您的自动化程度越高更好。
主脚本(即legacy/bin/full-migration
)应该执行整个过程(即获取旧生产数据库的新副本,(重新)创建新数据库和表,运行整个迁移)和它应该与您在生产服务器中部署新版本后最终运行的进程完全相同(仅使用不同的配置)。它允许您在开发环境中彻底测试它。
因为转换可能需要很长时间,所以记录每个操作都是有益的(普通print action + object_id
应该这样做)。通常会有一些行有一些意外的差异,这些行会导致脚本崩溃或导致引用完整性错误。在这样的情况下,很好地查看它是哪个对象,以便您可以立即转到数据库,检查数据,相应地更新脚本并再次运行失败的步骤。
对我来说非常有用的一件事是使用ORM为遗留数据库表定义模型类。我在Django中做了几次这样的事情,它支持多个数据库连接和每个模型的路由,所以我能够编写看起来大致相似的脚本(Python):
from legacy import models as old
from catalog import models as new
# Loop through all products from the legacy DB
for old_product in old.Product.objects.all():
# Create an instance of the new product model class
new_product = new.Product()
# Copy and modify attributes as needed
new_product.name = old_product.product_name.strip()
# ...
# Save it to the new database
new_product.save()
此外,新架构越具有限制性(即,尽可能使用NOT NULL,外键检查等),因为它可以帮助您查看关于旧架构的假设在哪里是错误的,并且还可以防止错误进入新系统的数据(InnoDB作为MySQL的后端是一个好主意)。
其他良好做法是尽可能保留新数据库中的旧主键。如果您在迁移后在新数据中看到一些奇怪的内容,则可以返回并按旧系统中的ID查找该项目。
答案 2 :(得分:4)
重写的第一步是完全理解当前的数据结构和在其上运行的代码。可能有一些数据显得多余,但代码要求它出于某种奇怪的原因。设计很糟糕吗?可能 - 但要确保您完全理解写入或访问数据的每一段代码,这样您就可以确定可以删除的内容,必须重构的内容以及必须保留的内容。
工具可以帮助自动化流程 - 但是如果不深入掌握当前系统,他们可以将您自动化到一个角落。
我会设计新的数据结构,编写脚本以将旧结构传输到新结构,然后测试功能。如果有问题,请更改新结构和/或导入脚本,然后再次运行数据传输例程并重复整个过程,直到确保没有数据或功能丢失。此时,安排一个日期来关闭旧系统,进行数据迁移,然后启动新系统。
当然,缺少这一切是在新/改进系统上培训用户。这至关重要!不要将其排除在您的计划之外,否则由于用户的不满,最好的新闪亮改进系统将会沉没。
答案 3 :(得分:2)
要考虑的一件事......
为什么不在视图后面隐藏新的,固定的,闪亮的架构,使其看起来像旧的?
这意味着您在同一数据上有2个客户端代码库,但每个数据库中都有自己的“API”。
这也意味着旧系统实际上从未在“上线”时关闭。
答案 4 :(得分:2)
首先,在设计新结构时,包括用于保存旧系统及其来源表的记录标识符的列。您可以在移动被证明是成功之后删除它们,但是它们将极大地帮助迁移数据并在迁移后测试它是否正确以及在用户对所看到的内容感到惊讶时回答有关数据来源的问题。如果旧数据没有PK,则使用某种类型的自动编号字段创建它们。
从父表中开始工作。如果地址存储在多个位置,请确定要从中获取地址的顺序,如果存在多个不同的记录,则会优先考虑哪个顺序。您可能还想存储不同的地址(地址表是一对多的人员表是?)但您可能需要提供其他地址类型。
您需要处理与新数据类型或大小或约束不匹配的旧数据问题(假设您需要某些内容并且它们没有值)。在开始之前决定如何处理并从利益相关者那里获得回报。如果需要街道1并且您只有城市和州,则可能需要使用“未知”的标记。
发送转换中转换的任何数据以符合新标准,或者您无法弄清楚如何更改为异常表。利益相关者或用户可能必须处理它们以获取新的必需数据或告诉您要更改的内容。
可能你需要多次运行。首先在开发框上,然后在QA框上。当转移到prod时,如果tranisition花费的时间比你能够承受的时间长,你可能需要在启动前一次移动大部分数据,然后在启动时移动新的或更改的数据。
有很多工作要做,3个月对于这种迁移来说非常紧张。祝你好运。