【问题标题】:Is Inconsistent state allowed in a transaction?事务中是否允许不一致状态?
【发布时间】:2020-04-24 21:13:59
【问题描述】:

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

tblPrimaryKey

PkId        
-----
1
2
3

tblForeignKey

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

我有 2 个表,一个引用另一个(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

【问题讨论】:

  • 你到底想做什么?
  • 您是说不能插入到 tblPrimaryKey 表中,因为 PkId(或 Id)列是身份种子列?
  • @kane :不,这里与身份无关

标签: sql transactions


【解决方案1】:

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

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

这些都与外键无关,外键是确保引用完整性的一种手段,这是另一回事(尽管相关)。

至于您要做什么,我知道在 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 无效。

【讨论】:

  • 这确实是 imo 原子的意思。不应该看到事务中间的不一致,因为事务是不可分割的。
  • 彼得:你正试图应用“原子”来认为它不适合应用。 “不可分割”仅仅意味着当你完成时你不能只剩下交易的一部分。这并不意味着引用引擎不能再抛出错误,而是意味着表不能抛出 PK 冲突错误或编译器不能抛出语法错误。
  • 但是你能回答这个问题吗?所有定义似乎都指向允许其间不一致状态的方向,无论是参考还是其他。
  • 没有什么我知道的指向那个方向。为关系数据库定义的原子性与此无关。
【解决方案2】:

最干净的解决方案是延迟外键约束。这会将约束的检查推迟到COMMIT 时间,允许在事务期间暂时违反它。不幸的是,此功能显然在 SQL Server 中不可用。在确实支持延迟约束的系统上,可以使用类似以下的方法:

alter table tblForeignKey
  modify constraint YourFKNameHere
    deferrable
    initially deferred;

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

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

set constraint YourFKNameHere deferred;

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

【讨论】:

  • 除了 deferred 不是 SQL Server 关键字(并且问题指出 SQL 2005) 删除和重新创建约束是可能的,但是在 SLQ Server 中会导致一些相当讨厌的可伸缩性问题(添加或删除约束需要sch-M 锁)
  • 这不是 SQL Server 解决方案,任何版本
  • 感谢指正;答案相应调整。从问题的风格来看,“通用数据库事务”和缺少“sqlserver”标签似乎问题可能更笼统。 :)
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    现在我有一些逻辑可以改变 主键的表,通过删除 并重新插入钥匙。

    听起来不像是 DELETE/INSERT 对,您应该只更新有问题的行? 要么,要么您必须先删除 tblForeignKey 中的密钥,然后重新创建它。

    【讨论】:

      猜你喜欢
      • 2010-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-09
      • 2012-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多