[英]Is Inconsistent state allowed in a transaction?
我有一个关于交易的非常简单的问题。 (在 sql server 2000 中,但我想它适用于一般的 db. 事务)。
主键
PkId
-----
1
2
3
外键
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 是从视图构建表的发布脚本的一部分,然后更新这些表。 由于视图包含发布模型,视图的更改是在那里进行的,而且只在那里进行。 脚本的其余部分不能依赖列名来进行更新。
当然我可以查询这些列名,但当时看起来很麻烦,所以我选择不这样做,而是删除约束并重建它们。 现在我必须承认我对那个解决方案感到不舒服,所以现在我确实使用了更新。 我写了一个 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
但我的问题是:我了解到事务是原子的,因此事务内部的不一致状态是允许的。
这不是“原子”的意思。 原子的意思是“不可分割的”,对于数据库来说,这只是意味着事务是全有或全无的事情。 事务完整性要求事务要么完全提交,要么完全回滚。
这些都与外键无关,外键是确保参照完整性的一种手段,这是另一回事(虽然相关)。
至于你想要做什么,我知道在 SQL Server 2005 中你可以暂时禁用 FK,这可能是在 2000 年。 然而,这通常不被认为是最佳实践。 相反,BP 要么
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)。
NOTE1:我无权访问 SQL 2000,所以我不知道上述是否适用。 它在 2005 年工作。
注 2:“DEFERRABLE”是 Oracle 设置。 它对 SQL Server 无效。
最干净的解决方案是推迟外键约束。 这会将约束的检查推迟到COMMIT
时间,允许在事务期间暂时违反约束。 不幸的是,此功能在 SQL Server 中显然不可用。 在确实支持延迟约束的系统上,类似以下内容将起作用:
alter table tblForeignKey
modify constraint YourFKNameHere
deferrable
initially deferred;
某些系统不允许您更改约束的延迟性,在这种情况下,您必须重新创建约束(可能还有表)。
SET CONSTRAINT[S]
语句可用于切换约束的延迟,例如在事务开始时:
set constraint YourFKNameHere deferred;
根据我的经验,ACID 属性虽然明显不同,但往往可以协同工作。 例如,在您的问题中,您正在尝试进行暂时无效的更新。 其他用户在您提交之前不会看到您的任何更改(隔离、原子性)(持久性),并且您的事务的任何部分都不会产生任何影响(原子性),除非您的事务以数据库处于一致状态(一致性)结束。
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
现在我有一些逻辑可以通过删除和重新插入一个键来改变主键的表。
听起来不像 DELETE/INSERT 对,你应该只是更新有问题的行? 要么,要么您必须先删除 tblForeignKey 中的密钥,然后重新创建它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.