更新关系数据的算法

时间:2009-07-04 17:29:23

标签: database foreign-keys constraints

在存在数据库约束的情况下,通过插入,更新和删除行来执行更新数据库的任务有哪些算法?

更具体地说,在要删除的行的图像之前,在要插入的行的图像之后,以及要更新的行的两个图像都在存储器中之前。这些行可能适用于多个表。确切的更新序列要么未知或尚未保留 - 只有数据库最终必须反映的前映像和后映像才是已知的。

数据库包含主键,外键和唯一索引约束。问题是找到命令序列以使数据库保持最新。为简单起见,我愿意指定永远不会修改行的主键。

数据库系统不支持延迟约束检查。 (对于这样的数据库,解决方案是微不足道的)。我还必须制定规则,即插入后主键列可能不会更新,并且不允许删除行并重新插入具有相同主键的行,即使某些算法可能会发现方便这样做。 (这对于数据库系统自动生成所有主键的常见情况是必要的。)

算法是什么:

  1. 假设必须始终强制执行外键约束,但不使用唯一索引。
  2. 假设必须始终强制执行外键约束和唯一索引约束。
  3. 我要求两者,因为我认为#1可能相当简单。

    编辑:这里的目标是以一般(或接近通用)的方式解决缺少延迟约束检查的问题。我认为高质量的ORM包必须这样做。 我想要解释算法,无论是你在这里提供的还是在学术论文中提供的,等等。我不会考虑指向软件包或源代码的指针来回答这个问题。

    NAIVE ALGORITHM:

    循环遍历表,并为添加,更改或删除的每一行分别生成一个INSERT,UPDATE或DELETE语句。 循环生成的语句并应用于数据库。如果语句未能应用,请继续使用其他语句。 重试失败的语句。继续迭代,直到没有更多失败或传递成功执行没有语句。 如果语句仍然存在,请尝试临时调整有问题的列中的数据,以尝试使它们成功。

    这是一个丑陋的暴力算法,并且弄清楚“临时调整”部分是它自己的挑战。所以,我想要一个改进的完整算法。

    EDIT2:

    RBarryYoung发布了一个接近(但没有雪茄)的答案,以完全解决方案#1,同时解决最常见的情景#2问题。以下是我在应用程序中看到经常但尚未到达解决方案的场景#1更新模式的示例。 DELETE / UPDATE-INSERT在场景#1中很多时候都是正确的,但诀窍是弄清楚何时偏离它。我还怀疑偏离它可以放大每个场景#2中出现的UNIQUE问题,也可能增加我对解决场景#2的兴趣。

    请注意,没有循环,也没有修改任何主键。但是,会修改父项的外键。

    CREATE TABLE A
    (
        AId INT NOT NULL PRIMARY KEY
    )
    
    CREATE TABLE B
    (
        BId INT NOT NULL PRIMARY KEY,
        AId INT NOT NULL FOREIGN KEY REFERENCES A (AId)
    )
    
    CREATE TABLE C
    (
        CId INT NOT NULL PRIMARY KEY,
        AId INT NOT NULL FOREIGN KEY REFERENCES A (AId),
        BId INT NOT NULL FOREIGN KEY REFERENCES B (BId)
    )
    

    在图像之前:

    A (1)
    B (1,1)
    C (1,1,1)
    

    图片后:

    A (1)
    B (2,1) [To be deleted: (1,1)]
    C (1,1,2)
    

    排序顺序:A,B,C

    第一个命令是DELETE B(1,1),由于C(1,1,1)而失败。

    请注意,如果C中的第三列允许NULL(在这种情况下它不是这样),纯解决方案可能会在早期传递中将其清空,因为这将允许给定算法正常进行并具有其通常的优点处理大多数情景#2问题。解决这个问题的一个很好的解决方案也需要考虑这样的事情。这个问题的完整普遍性无疑是一个引人入胜的问题。

6 个答案:

答案 0 :(得分:4)

为什么你甚至试图这样做?正确的方法是让数据库引擎推迟检查约束,直到提交事务为止。

您提出的问题在一般情况下是难以处理的。如果您只考虑要在数据库中更新的行中的外键的传递闭包,则只能在图形描述树的情况下解决此问题。如果图中有一个循环,您可以通过用NULL替换外键值来中断循环,那么您可以重写一个SQL并添加另一个SQL以便稍后更新该列。如果你不能用NULL替换键值那么它就无法解决。

正如我所说,正确的方法是关闭约束,直到所有SQL都已运行,然后重新打开它们进行提交。如果不满足约束,则提交将失败。 Postgres(例如)有一个功能,使这很容易。

答案 1 :(得分:1)

我写了一次,但这是别人的IP,所以我不能详细说明。但是,我愿意告诉你教会我如何做到这一点的过程。这是一个制作客户“数据库”的卷影副本的工具,该数据库位于salesforce.com上,用.NET 1.1编写。

我开始以暴力方式(从模式创建DataSet和数据库,关闭DataSet中的约束,遍历每个表,加载行,忽略错误,对于尚未在表中的行,重复)直到更多行添加,或没有更多错误,或没有更改错误数,然后将DataSet转储到DataBase,直到没有错误等)。

蛮力是起点,因为我们根本无法做到这一点。 salesforce.com的“架构”不是真正的关系架构。例如,如果我没记错的话,有些列是与几个父表之一相关的外键。

这需要永远,即使在调试时也是如此。我开始注意到大部分时间都花在处理数据库中的约束违规上。我开始注意到约束违规的模式,因为每次迭代都会慢慢收敛,以便保存所有行。

我所有的启示都是由于我的无聊,看着系统一次坐在100%CPU附近15-20分钟,即使是一个小型数据库。 “必要性是发明的母亲”,“等待另外20分钟的同一行的前景,往往集中精神”,我想出了如何加快速度超过100倍。

答案 2 :(得分:1)

好吧,我认为就是这样,虽然Unique Key很难弄明白。请注意,SQL执行中遇到的任何错误都应导致整个事务的完全回滚。

<强>更新   我实施的原始订单是:

每个表,BottumUp(表格的所有删除)  每个表,TopDown(所有更新,然后所有插入)

在发布一个反例之后,我相信我知道如何解决限制性问题(问题#1,没有UCs):通过将订单更改为:

每个表,TopDown(所有插入)  每个表,TopDown(所有更新)  每个表,BottumUp(全部删除)

这肯定不适用于Unique Constraints,据我所知,这将需要基于行内容的依赖排序(与我目前使用的静态表FK依赖排序相反)。特别困难的是,它可能需要获取除已更改的记录内容之外的信息(特别是检查UC冲突值的存在以及中间步骤的子依赖记录)。

无论如何,这是当前版本:

Public Class TranformChangesToSQL
 Class ColVal
    Public name As String
    Public value As String  'note: assuming string values'
 End Class

 Class Row
    Public Columns As List(Of ColVal)
 End Class

 Class FKDef
    'NOTE: all FK''s are assumed to be of the same type: records in the FK table'
    ' must have a record in the PK table matching on FK=PK columns.'
    Public PKTableName As String
    Public FKTableName As String
    Public FK As String
 End Class

 Class TableInfo
    Public Name As String
    Public PK As String                     'name of the PK column'
    Public UniqueKeys As List(Of String)    'column name of each Unique key'
    'This table''s Foreign Keys (FK):'
    Public DependsOn As List(Of FKDef)
    'Other tables FKs that point to this table'
    Public DependedBy As List(Of FKDef)
    Public Columns As List(Of String)
    'note: all row collections are indexed by PK'
    Public inserted As List(Of Row)     'inserted after-images'
    Public deleted As List(Of Row)      'deleted before-images'
    Public updBefore As List(Of row)
    Public updAfter As List(Of row)
 End Class

 Sub MakeSQL(ByVal tables As List(Of TableInfo))
    'Note table dependencies(FKs) must NOT form a cycle'

    'Sort the tables by dependency so that'
    ' child tables (FKs) are always after their parents (PK tables)'
    TopologicalSort(tables)

    For Each tbl As TableInfo In tables
        'Do INSERTs, they *must* be done first in parent-> child order, because:'
        '   they may have FKs dependent on parent inserts'
        '   and there may be Updates that will make child records dependent on them'
        For Each r As Row In tbl.inserted
            Dim InsSQL As String = "INSERT INTO " & tbl.Name & "("
            Dim valstr As String = ") VALUES("
            Dim comma As String = ""
            For Each col As ColVal In r.Columns
                InsSQL = InsSQL & comma & col.name
                valstr = valstr & comma & "'" & col.value & "'"
                comma = ", "    'needed for second and later columns'
            Next
            AddSQL(InsSQL & valstr & ");")
        Next
    Next

    For Each tbl As TableInfo In tables
        'Do UPDATEs'
        For Each aft In tbl.updAfter
            'get the matching before-update row'
            Dim bef As Row = tbl.updBefore(aft.Columns(tbl.PK.ColName).value)
            Dim UpdSql As String = "UPDATE " & tbl.Name & " SET "
            Dim comma As String = ""
            For Each col As ColVal In aft.Columns
                If bef.Columns(col.name).value <> col.value Then
                    UpdSql = UpdSql & comma & col.name & " = '" & col.value & "'"
                    comma = ", "  'needed for second and later columns'
                End If
            Next
            'only add it if any columns were different:'
            If comma <> "" Then AddSQL(UpdSql & ";")
        Next
    Next

    'Now reverse it so that INSERTs & UPDATEs are done in parent->child order'
    tables.Reverse()

    For Each tbl As TableInfo In tables.Reverse
        'Do DELETEs, they *must* be done last, and in child->paernt order because:'
        '   Parents may have children that depend on them, so children must be deleted first,'
        '   and there may be children dependent until after Updates pointed them away'
        For Each r As Row In tbl.deleted
            AddSQL("DELETE From " & tbl.Name & " WHERE " & tbl.PK.ColName & " = '" & r.Columns(tbl.PK.ColName).value) & "';"
        Next
    Next

 End Sub
End Class

答案 3 :(得分:1)

不,我觉得这并不令人着迷。我也没有发现这个问题也很令人着迷,而且在那个话题上也确实存在强烈甚至暴力地反对我的人。

当你说“它有实际应用”时,你的意思是说“这个问题的解决方案有实际用途”吗?我建议根据定义,不存在的解决方案不能具有“实际应用”。 (我确实建议你所寻找的解决方案不存在,就像圆的正交一样。)

你提出了一些关于“当其他应用挂起级联删除时......”的问题。您最初的问题陈述中没有提及任何“其他应用程序”。

我觉得更令人着迷的问题是“如何建立一个足够好的DBMS,以便程序员不再面对这类问题,不再被迫提出这类问题”。这样的DBMS支持多重分配。

祝你好运。

答案 4 :(得分:0)

听起来您正在寻找数据库差异工具。这样的工具会查找两个表(或两个数据库)之间的差异,并生成必要的脚本以对齐它们。

有关详细信息,请参阅以下帖子:
https://stackoverflow.com/questions/104203/anyone-know-of-any-good-database-diff-tools

答案 5 :(得分:0)

OpenDbDiff有源代码可用。你可以看一下并找出算法。

http://opendbiff.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=25206