在事务中允许不一致状态吗?

4
我有一个关于事务的非常简单的问题。(在SQL Server 2000中,但我想它适用于一般的数据库事务)。 tblPrimaryKey
PkId        
-----
1
2
3

tblForeignKey

Id   ForeignKey  
---- ----- 
1    1
2    2
3    3
4    1

我有两个表,一个引用另一个表(tblForeignKey.ForeignKey引用tblPrimaryKey.PkID)。现在我有一些逻辑,通过删除和重新插入键来更改主键表。

删除后,数据库当然处于不一致状态。我看了我的一个旧脚本,在其中我首先删除了关系,然后重新创建了它。但我的问题是:我了解到事务是原子的,因此在事务内允许不一致状态。

所以我想这样做应该可以:

BEGIN TRAN eg

    DELETE tblPrimaryKey WHERE PkId = 3     
    INSERT INTO tblPrimaryKey  SELECT 3

COMMIT TRAN eg

但是这种方法不起作用。有人能提供一个应用此逻辑的工作事务示例吗?
更新:
一致性 这个特征意味着在事务之前和之后数据库应该是一致的。
在任何情况下,部分事务都不能提交到数据库中,因为那会使数据库处于不一致状态。
这是否意味着在事务中不一致是可能的?
更新:
有些人问我为什么在这种情况下没有使用更新。有点复杂,但我试着解释一下:所需的sql是发布脚本的一部分,它从视图构建表,然后更新这些表。由于视图包含发布模型,因此对视图进行了更改,仅仅只是在那里进行了更改。脚本的其余部分不能依赖列名来进行更新。
当然,我可以查询这些列名,但当时似乎有点麻烦,所以我选择不这样做,而是删除约束并重新构建它们。现在我必须承认,我自己对这个解决方案感到不舒服,所以现在我确实使用更新。我编写了一个存储过程来执行这个操作,如果有其他解决方案,请让我知道。
CREATE PROC usp_SyncRecords
(
 @tableName1 as nvarchar(255),
 @tableName2 as nvarchar(255), 
 @joinClause as nvarchar(255),
 @whereClause as nvarchar(1000)
)
-- this proc updates all fields in table 1 that have corresponding names 
-- in table2 to the value of the field in table2.
AS 
BEGIN 
    DECLARE @sqlClause nvarchar(4000)
    DECLARE @curFieldName nvarchar(255)
    DECLARE @sqlColumnCursorClause nvarchar(1000)
    SET @sqlClause = 'UPDATE [' + @tableName1 + '] SET '

    -- get FieldNames for second table 
    SET @sqlColumnCursorClause = 
        'DECLARE cur CURSOR FAST_FORWARD FOR SELECT name FROM syscolumns ' + 
        'WHERE id=' + CAST(object_id(@tableName2) as nvarchar(50))

    EXEC sp_executeSql @sqlColumnCursorClause


    OPEN cur
        -- compose sqlClause using fieldnames
        FETCH NEXT FROM CUR INTO @curFieldName
        WHILE @@fetch_status <> -1 
        BEGIN 
            SET @sqlClause = @sqlClause + @curFieldName  + '=' +
                                                      @tableName2 +  '.' + @curFieldName  + ','
            FETCH NEXT FROM CUR INTO @curFieldName
        END

    CLOSE cur 
    DEALLOCATE cur 

    -- drop last comma 
    SET @sqlClause = LEFT(@sqlClause,LEN(@sqlClause) -1)

    -- adding from/join/where clauses 
    SET @sqlClause = @sqlClause + ' FROM [' + @tableName1 + '] INNER JOIN [' + @tableName2 + '] '
               + 'ON ' + @joinClause +  ' WHERE '  +  @whereClause

    EXEC sp_executeSQL @sqlClause

END

你实际上想要做什么? - Mitch Wheat
你是说因为PkId(或Id)列是标识种子列,所以无法将数据插入到tblPrimaryKey表中? - Kane
@kane:不,这里与身份无关。 - Peter
4个回答

4
但我的问题是:我了解到事务是原子性的,因此在事务内允许存在不一致状态。
这并不是“原子性”所意味的。原子性意味着“不可分割”,对于数据库来说,这仅意味着事务是一个全有或全无的过程。事务完整性要求事务被完全提交或完全回滚。
这与外键没有任何关系,外键是确保引用完整性的一种方式,这是一种不同的东西(尽管相关)。
至于你想做的事情,我知道在SQL Server 2005中,您可以暂时禁用FK,这也可能适用于2000。然而,这通常不被认为是最佳实践。相反,最佳实践是:
1)不删除父键值,而是更新行,同时保留父键值,或者
2)如果您打算永久删除(或更改)父键,则应首先删除或重新分配子记录。
结构上的不一致从不应该对用户可见(如果是这样,那么您的结构已经损坏)。
事务上的不一致仅允许在事务中出现。它不应该在事务之外可见(除了隔离级别低于Serializable允许某种程度的不一致)。
引用不一致与这两者无关。但是,在大多数情况下,可以通过使用NOCHECK选项禁用引用完整性。
    -- Disable the constraint.
ALTER TABLE cnst_example NOCHECK CONSTRAINT FK_salary_caps;

--Do stuff that violates RI here:

-- Reenable the constraint.
ALTER TABLE cnst_example WITH CHECK CHECK CONSTRAINT FK_salary_caps;

然而,这并不是首选的方法。首选的方法是按正确顺序进行更改(这是直接来自BOL的)。

注意1:我没有访问SQL 2000,因此我不知道上述方法是否适用于该版本。它适用于2005年版。

注意2: "DEFERRABLE" 是 Oracle 的设置。它不适用于 SQL Server。


这确实是原子操作的含义。在事务进行中出现不一致性是不应该出现的,因为事务是不可分割的。 - Peter
Peter:你试图将“原子性(Atomic)”应用于它不应该应用的东西上。“不可分割(Indivisible)”只是意味着当你完成时,事务中不能剩下部分。这并不意味着引用引擎不能抛出错误,就像它也不意味着表不能抛出PK违规错误或编译器不能抛出语法错误一样。 - RBarryYoung
但是你能回答这个问题吗?所有的定义似乎都指向允许不一致状态的方向,无论是引用还是其他情况。 - Peter
据我所知,没有任何迹象指向那个方向。关系数据库中定义的原子性与此无关。 - RBarryYoung

2
最干净的解决方案是将外键约束延迟。这将推迟检查约束直到提交时间,允许在事务期间暂时违反它。不幸的是,SQL Server 显然没有此功能。在支持延迟约束的系统上,以下内容将起作用:
alter table tblForeignKey
  modify constraint YourFKNameHere
    deferrable
    initially deferred;

有些系统不允许您更改约束的延迟性,如果是这种情况,您需要重新创建约束(可能还要重新创建表)。

SET CONSTRAINT[S] 语句可用于切换约束的延迟性,例如在事务开始时:

set constraint YourFKNameHere deferred;

根据我的经验,ACID属性虽然明显不同,但通常会协同工作。例如,在您的问题中,您正在尝试进行暂时无效的更新。其他用户在您提交更改(Durability)之前将看不到任何更改(Isolation,Atomicity)。除非您的事务以数据库处于一致状态(Consistency)而结束,否则您的事务的任何部分都不会产生任何影响(Atomicity)。


除了延迟不是 SQL Server 关键字(并且问题说明了 SQL 2005),删除和重新创建约束是可能的,但在 SLQ Server 中会导致一些相当严重的可扩展性问题(添加或删除约束需要 sch-M 锁)。 - GilaMonster
这不是 SQL Server 的解决方案,适用于任何版本。 - gbn
感谢您的更正,答案已相应调整。从问题的风格、"general db. transactions"以及缺少"sqlserver"标签来看,这个问题似乎更偏向于一般性的。 :) - cheduardo

1

ACID中的一致性意味着只有有效数据才会被写入,而不是在事务中允许不一致的数据。

然而,为了解决这个特定的SQL问题,请假设ForeignKey列可以为NULL。

DECLARE @FKTabIDs (FKTabID int)

BEGIN TRAN eg

    INSERT FKTabIDs (FKTabID) SELECT [Id] FROM tblForeignKey WHERE ForeignKey = 3

    --Assumes NULL but could use any valid value
    UPDATE tblForeignKey SET ForeignKey = NULL WHERE ForeignKey = 3

    DELETE tblPrimaryKey WHERE PkId = 3         
    INSERT tblPrimaryKey SELECT 3

    UPDATE tFK
    SET ForeignKey = 3
    FROM tblForeignKey tFK JOIN @FKTabIDs tv ON tFK.[Id] =  tv.FKTabID
    --... or use exists, in etc if you prefer

COMMIT TRAN eg

0
现在我有一些逻辑可以改变主键表,通过删除和重新插入一个键。
听起来你应该更新相关的行,而不是使用 DELETE/INSERT ? 或者你需要先删除 tblForeignKey 中的键,然后重新创建。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接