交易中是否允许不一致状态?

时间:2009-06-07 07:17:30

标签: sql transactions

我有一个关于交易的非常简单的问题。 (在sql server 2000中,但我想它适用于一般的db。事务)。

tblPrimaryKey

PkId        
-----
1
2
3

tblForeignKey

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

我有2个表,一个引用另一个表(tblForeingKey.ForeignKey引用tblPrimaryKey.PkID)。现在我有一些逻辑通过删除和重新插入密钥来改变主键的表格。

删除数据库后,将处于不一致的状态。我看着我的旧剧本,在那里我第一次放弃了这段关系并在之后再创作。但我的问题是:我了解到事务是原子的,因此允许在事务内部处于不一致状态。

所以我觉得这样的事情应该有效:

BEGIN TRAN eg

    DELETE tblPrimaryKey WHERE PkId = 3     
    INSERT INTO tblPrimaryKey  SELECT 3

COMMIT TRAN eg

但这不起作用。有人能为我提供一个应用这种逻辑的工作交易的例子吗?

更新:

一致性 这个特征意味着数据库在事务之前和之后应该是一致的。

在任何情况下都不能将部分事务提交到数据库,因为这会使数据库处于不一致状态。

这是否意味着 中的交易不一致?

更新:

有些人问我为什么在这种情况下我没有使用更新。有点复杂,但我试一试:所需的sql是一个发布脚本的一部分,它从视图构建表,然后更新这些表。由于视图包含发布模型,因此在那里进行了视图的更改,并且仅在那里进行了更改。脚本的其余部分不能依赖列名来进行更新。

当然我可以查询这些列名,但当时看起来很麻烦,所以我选择不这样做,而是删除约束来重建它们。现在我必须承认我对这个解决方案感到不舒服,所以现在我确实使用了更新。我写了一个sproc来做到这一点,如果有人提出其他解决方案,请告诉我。

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

4 个答案:

答案 0 :(得分:4)

  

但我的问题是:我了解到事务是原子的,因此允许在事务内部处于不一致状态。

这不是“原子”的意思。原子意味着“不可分割”,对于数据库而言,这仅仅意味着交易是全有或全无的事情。 Transactional 完整性要求事务完全提交或完全回滚。

这些都与Foreign-Keys无关,外键是确保参照完整性的手段之一,这是另一回事(虽然相关)。

至于你想要做什么,我知道在SQL Server 2005中你可以暂时禁用FK,这也可能在2000年。但是,这通常不被认为是最佳做法。相反,BP要么

1)NOT删除父键值,但改为更新行,同时保留父键值OR,

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无效。

答案 1 :(得分:2)

最干净的解决方案是延迟外键约束。这将推迟检查约束直到COMMIT时间,允许在事务期间临时违反约束。不幸的是,这个功能显然在SQL Server中不可用。在支持延迟约束的系统上,类似下面的内容将起作用:

alter table tblForeignKey
  modify constraint YourFKNameHere
    deferrable
    initially deferred;

某些系统不允许您更改约束的延迟,在这种情况下,您必须重新创建约束(可能还有表)。

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

set constraint YourFKNameHere deferred;

根据我的经验,ACID属性虽然明显不同,但往往可以协同工作。例如,在您的问题中,您正在尝试进行暂时无效的更新。其他用户在您提交之前不会看到任何更改(隔离,原子性)(持久性),并且您的事务的任何部分都不会产生任何影响(原子性),除非您的事务以数据库处于一致状态(一致性)结束。

答案 2 :(得分:1)

Consistency in 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

答案 3 :(得分:0)

  

现在我有一些改变它的逻辑   主键的表格,删除   并重新插入一把钥匙。

声音而不是DELETE / INSERT对,你应该只更新有问题的行? 要么是这样,要么首先必须删除tblForeignKey中的密钥,然后重新创建。