2009-06-07 32 views
3

我有一个关于交易的非常简单的问题。 (在SQL Server 2000中,但我想它适用于一般的数据库事务)。交易中允许的状态不一致吗?

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 

但是,这是行不通的。有人能给我提供一个应用这种逻辑的工作事务的例子吗?

更新:

一致性 这一特性意味着数据库应该是之前和交易后一致。

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

这不意味着的交易不一致是可能的吗?

UPDATE:

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

Ofcourse当然我可以查询这些列名,但它在当时看起来像一个麻烦,所以我选择不去,而是放弃约束重建它们。现在我必须承认,我对这种解决方案感到不舒服,所以现在我确实使用了更新。我写了一个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 
+0

你究竟在做什么? – 2009-06-07 07:33:02

+0

你是说你不能插入tblPrimaryKey表,因为PkId(或Id)列是身份种子列? – Kane 2009-06-07 07:41:34

+0

@ kane:不,与这里的身份无关 – Peter 2009-06-07 07:57:37

回答

2

最干净的解决方案将是使递延外键约束。这将推迟检查约束,直到COMMIT时间,使其在交易过程中被暂时违反。不幸的是,这个功能显然在SQL Server中不可用。在那做支持延迟约束系统,像下面将工作:

alter table tblForeignKey 
    modify constraint YourFKNameHere 
    deferrable 
    initially deferred; 

一些系统不允许你改变一个约束的延迟性,在这种情况下,你将不得不重新创建约束(以及可能的表)。

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

set constraint YourFKNameHere deferred; 

根据我的经验,ACID属性虽然明显不同,但倾向于一起工作。例如,在您的问题中,您正在尝试执行暂时无效的更新。除非您的交易以数据库处于一致状态(一致性)结束,否则其他用户将不会看到您的任何更改(隔离,原子性),直到您提交它们(持久性),并且您的事务的任何部分都不会有任何影响(原子性)。

4

但我的问题是这样的:我了解到,交易是原子,所以交易的内部不一致的状态,是允许的。

这不是“原子”的意思。原子的意思是“不可分割的”,对于数据库来说,这仅仅意味着一个交易是一件完全没有事情的事情。交易性完整性要求事务要么完全提交,要么完全回滚。

这些都与外键无关,外键是保证参考完整性的手段之一,这是一个不同的事情(尽管相关)。

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

1)不能删除父键值,但更新该行代替,同时保留父键值,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

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 
0

现在我有一些逻辑之改变 TABEL的主键,删除 并重新插入一个关键。

听起来不像DELETE/INSERT对,而应该更新相关行? 要么,要么你必须先删除你的tblForeignKey中的密钥,然后重新创建它。