有效管理数据变更
Posted
技术标签:
【中文标题】有效管理数据变更【英文标题】:Effective management of data changes 【发布时间】:2014-08-09 10:40:32 【问题描述】:我有一张名为 Bookings 的表。此表包含表示为特定服务进行的预订的数据,其中包含许多变量。
不久前,我遇到了当前数据结构的问题,即任何影响时间、日期或价格的预订更改都会影响其他相关的财务记录、日期的预订列表等。
当时我的解决方案是创建一个修改表来跟踪对预订所做的任何更改。然后,每当 Booking 模型被要求返回预订时,它都会添加所做的修改(在 afterFind()
Cake 回调中)并呈现最新版本的预订,类似这样(请原谅 Paint 绘图):
当您要求 Booking 模型返回预订 #1234 时,此方法可以正常工作。它返回预订的最新表示,包括所有修改(相互叠加),包括一个包含所有修改和原始预订数据以供参考的数组。
我的问题是我最近意识到我需要能够使用自定义条件查询这个模型,如果其中一个条件在其中一个修改中实现,结果将不会' 不匹配,因为模型正在搜索原始记录而不是最终呈现的记录。我查询模型以返回 abc
为蓝色(非灰色)的行的示例:
在该示例中,模型直接查看 abc
为蓝色的行的原始数据,并且不返回此结果,因为蓝色值位于 之后附加的修改中找到原始结果。
我现在所做的是将查询放入 Booking 模型的 beforeFind()
回调,以查找符合给定条件的修改,加入预订以确保任何其他条件仍然匹配。当它在上面的示例中返回蓝色时,它将结果存储在一个数组中作为类属性并继续使用常规的find()
,但排除该预订的 ID 不会被返回(因为我们发现它的更新版本)。然后它将它们合并在一起,在afterFind()
中再次对它们进行排序等。
这行得通,虽然我希望它有点冗长。
毕竟,我意识到在此应用程序的其他部分中,有些模型手动加入预订表并搜索预订。所以现在我需要一种方法,能够将修改合并到所有这些手动连接中,直接连接到 mysql 中的表,而不影响原始数据,最好不要更改太多代码。
我的想法是我需要删除手动连接并改为创建模型关联。当我查询拥有Many Bookings 的Customer 模型(将修改应用于每个预订)时,Booking 模型的beforeFind()
和afterFind()
是否仍会运行?
我的另一个选择是通过删除可能包含在修改中的任何条件从 MySQL 返回更多的行,然后使用 php 根据我的搜索条件过滤结果。这个选项让我有点害怕,因为如果没有这个标准,结果集可能会很大......
如何实现这种数据结构?我的关键需求仍然是我不想更改原始的Booking记录,而是在顶部添加Modification记录,但我需要能够通过模型查询预订(包括修改)。
我想尝试尽可能多地将这种集成保留在幕后,这样我就不必通过整个应用程序来更改 n
的查询数量,如下所示:
$get_blue = $this->Booking->find('all', array(
'conditions' => array(
'Booking.abc' => 'blue'
)
));
我希望能够隐式包含对预订所做的任何修改,以便在上述查询中返回最新的预订。
另一个问题是当 Booking 模型手动加入搜索查询时,如下所示:
$get_transactions_on_blue_bookings = $this->Transaction->find('all', array(
'joins' => array(
array(
'table' => 'sql_bookings_table', // non-standard Cake format, I know - it's an example
'alias' => 'Booking',
'type' => 'LEFT',
'conditions' => 'Booking.booking_id = Transaction.booking_id'
)
),
'conditions' => array(
'Booking.abc' => 'blue'
)
));
如您所见,上面的查询不会包含我上面MSPaint示例中的修改,因为它是在SQL中手动加入表(修改集成在Booking的before
和afterFind()
回调函数中型号)。
对此的任何帮助将不胜感激。
编辑
我知道这已经够长了,但我想补充一点,我想跟踪这些变化而不更新原始记录的原因是 财务 方面无法改变,因为它会影响报告。
到目前为止,我能看到的最快和最简单的解决方案是在所有情况下直接对原始预订应用修改,除非它影响财务信息,但仍将其作为修改进行跟踪(因为我目前不需要基于搜索关于此信息)。
【问题讨论】:
我们最近遇到了一个类似的问题,即不适合用途的旧数据结构。我们尝试了您在此处使用的方法,即在顶部添加一个层来处理更改,但它最终产生了太多错误。最后,我们从头开始重新设计数据结构,并编写了一个 CLI 导入器来移动所有现有数据。这样做意味着我们可以慢慢地将面向客户端的功能迁移到新系统中,因为这两个系统都在跟踪数据。当我们对新系统完全满意时,我们只需从项目中删除遗留代码。希望对您有所帮助。 @orciny 确实有帮助。我认为我们最终也将不得不重新设计其工作方式。 【参考方案1】:听起来您正在尝试实现 Temporal Database。时间支持是 ANSI/ISO SQL:2011 标准的主要补充之一。 MySQL(像大多数 RDBMS 一样)落后于标准。将时态数据库视为 CVS/SVN/Git 的 DBMS 等价物。
相比之下,我们使用的没有时间特征的传统数据库可以称为当前数据库。
在当前数据库中,如果您尝试实现时间支持,您可能会在很多方面因不同的方法而失败:
单表方法。当您需要进行修改时,您可以在原始记录上执行UPDATEs
,除非您有某种本土触发器/审计逻辑,否则没有历史痕迹。即使您有审核/更改日志,您也必须进行一些丑陋的挖掘来重建更改历史记录。
双表方法。您无需就地进行修改,而是将数据拆分为两个表,一个包含基本/原始记录(例如预订),另一个包含用于您的更改/修改/增量的表。然后至少您保留了原始数据,但是您必须再次编写复杂的逻辑来查看原始数据并进行分层修改。如果您只想应用一些的修改,情况会更糟。
预先计算的结果表方法。您保留 3 个或更多表:基本记录、修改以及尝试始终拥有结果的表(保持最新的基本 + 修改)。祝您在每次执行INSERTs
时编写触发器和程序来执行此计算,如果需要UPDATE
或DELETE
,天堂会帮助您。设置很脆弱,可能会不同步,例如死锁和回滚。如果您不在数据库中使用触发器/过程执行此操作,您可以尝试在应用程序代码中实现结果计算,但祝您好运——多线程使用者可能会变得丑陋。而且,您仍然无法轻松访问仅应用了一些修改的结果。
结论:如果您不限于 MySQL,您应该真正考虑使用具有内置时间支持的数据库。否则,您将重新实现***。
【讨论】:
您好 Joshua - 感谢您的回答,哪些数据库内置了时间支持? 这对我来说也是新的,但如果你想保持开源,那么带有 Temporal Postgres 贡献包的 PostgreSQL 看起来很有趣 - pgfoundry.org/projects/temporal。这些链接可能会导致其他起点 en.wikipedia.org/wiki/… 。似乎其中很多都围绕着有效地使用PERIOD
数据类型。我刚刚也从 IBM 浏览了这个,其中有很多关于这个主题的内容:ibm.com/developerworks/data/library/techarticle/…
嗨 Joshua,我给了你赏金,因为你的答案是最有用的,尽管 MySQL 中还不存在时间数据库支持。感谢您的回答!
甜蜜,谢谢。至少如果您仍然使用 MySQL,您将了解时间范式,而不会盲目进入。【参考方案2】:
如果您不将修改应用于原始记录,而是进行相反的操作并将原始记录应用于修改,该怎么办?您可以修改修改表(或新表)以保存应用了修改的原始记录,并将您的搜索定向到那里。
另一个想法是,如果需要保留的只是财务数据,为什么不将其保存在另一个字段或表中,并在需要时引用它呢? 我同意重新设计可能是长期解决方案的最佳/最聪明的方法,但我想我会把我的想法放在桌面上,以防他们能提供帮助。
【讨论】:
在短期内,我们决定这样做并单独保存财务数据,同时立即应用所有其他更改(并且仍然保留修改日志)。关于将原始预订应用于修改 - 是否符合“有效管理”以潜在地复制数据?我可以看到每一行都被完全复制,因为数据库大小的增加非常迅速......? 一般来说,我倾向于尽可能避免重复数据,但我建议这样做,因为我不确定你能做什么,不能做什么,涉及多少数据等等。回答你的问题问题,我最初的反应是不,它不符合有效的管理,但在某些情况下它可能是一个值得的解决方案(取决于我列出的项目)。如果单独存储财务数据是一个可行的解决方案,那可能是我想要的短期解决方案。重写可能仍然是最好的长期解决方案,具体取决于您的实际实施。【参考方案3】:如果您在修改原始表之前使用备份表存储原始表中的数据怎么办?然后,您可以使用回滚功能将数据恢复到以前的状态。
这是我的数据库更新过程理论的流程图: http://i1371.photobucket.com/albums/ag300/joshua127/BookingFlowchartinsert_zps5c2d55f8.png
这是我的选择过程理论的流程图: http://i1371.photobucket.com/albums/ag300/joshua127/BookingFlowchartselect_zps702fa902.png
希望这会有所帮助,只是另一种看待它的方式。
附:为了保持财务信息不变,您可以编写更新函数来计算要更新的列数(基于列名的更新数组),并提供变量来单独保存这些列的特定值。您可以在 SQL 语句中引用数组索引 ($array['index']) 以使其动态化。
【讨论】:
嗨@jfh6 我不希望在任何时候复制该行,如果是这样的话,我早就应该这样做了。我的理由是,如果“预订”被编辑十次,那么您将有 11 行(10 次编辑和 1 次当前),所有列都包含相同数量的数据 -> 比我想要的多 10 倍以上的数据存储。如果我有一百万行,那么我将有一千万行具有相同的列数。我宁愿能够将每个修改后的字段存储为自己的行,这样它就只有四个字段:booking_id、field_name、modified_value 和 modified_date。小得多。【参考方案4】:在我看来,您需要的是一种表的历史记录,以便您能够知道当时发生了什么。
我通常通过创建一个名为的并行表来实现这种方法,类似于原始附加_history
到它。 Bookings_history
在你的情况下。结构与原始结构相似,但在列之前:
a) timestamp
,修改发生时保存
b)id
,用于识别原始表中的行
将在这两列上创建唯一索引。
每次发生修改时,在应用修改之前,您都将原始行复制到历史记录表中。然后将修改应用于原始表。这样做,历史表就像一个堆栈,您可以在其中保存原始数据的快照。
我特别喜欢这个模型,因为连接表和在历史表上应用搜索引擎可以通过与原始表类似的方式完成,因为结构非常相似。另外,如果你想知道修改,你只需要比较历史表的行。
我希望这会有所帮助。
【讨论】:
您好,感谢您的回答。我真的不想复制行 - 这是出于数据库大小的原因,否则我会这样做 不用担心。遵循此模型,您实际上并没有复制行。您一直在存储快照,这些快照彼此不同。就表的性能/大小而言,这是一个不同的问题,必须考虑其他因素,如硬件资源、数据增长速度等。无论如何,感谢您的反馈。【参考方案5】:从您已经收集到的答案很明显,无论您做什么,都需要进行一些或更多的重新设计。
我还没有看到并且我过去用来解决此类问题(即更改的订单)的解决方案之一是将所有内容保留在同一张表中并使用字段来区分它们。
您可以更改bookings
表,为每个预订添加一个递增的整数(即version_number
)和一个is_latest
字段。这样您就可以使用is_latest=true
查询以获取当前记录及其version_number
。如果为 0,则没有变化,如果大于 0,则有变化(该数字将等于更改的数量)。如果您从最新版本转到 0 或相反的方式,您将能够“倒带”或“重放”历史记录,并且每次您都将拥有您的应用无需修改即可理解的完整记录。
如果is_latest
被索引,查询速度将(几乎)等于原始表查询速度,当然如果您需要多次获取原始预订,您可以添加更多布尔值,例如is_original
。
这样做的好处是很可能只需要您更改 Booking
模型,但这取决于您的代码。
编辑:我相信这种方法最符合您对报告和财务记录的要求,因为您始终可以轻松获得原始记录(版本 0)。
【讨论】:
以上是关于有效管理数据变更的主要内容,如果未能解决你的问题,请参考以下文章