暂时禁用所有外键约束

Posted

技术标签:

【中文标题】暂时禁用所有外键约束【英文标题】:Temporarily disable all foreign key constraints 【发布时间】:2012-07-23 07:15:35 【问题描述】:

我正在运行一个 SSIS 包,它将把 FlatFiles 中的几个表的数据替换为数据库中的现有表。

我的包将截断表格,然后插入新数据。当我运行我的 SSIS 包时,由于外键而出现异常。

我可以禁用约束,运行我的导入,然后重新启用它们吗?

【问题讨论】:

来自未来的亲爱的人们:您可以一次禁用和重新启用数据库中的所有约束 - 请参阅 ***.com/a/161410 @brichins 有时数据库中没有这个未记录的存储过程。这给出了 0 条记录:SELECT * FROM sys.all_objects WHERE name like 'sp_MSforeach%';,然后尝试使用它,例如EXEC sp_MSforeachtable SELECT 1 AS FOO 给出错误消息Msg 2812, Level 16, State 62, Line 15 Could not find stored procedure 'sp_MSforeachtable'. 【参考方案1】:

禁用外键约束:

DECLARE @sql NVARCHAR(MAX) = N'';

;WITH x AS 
(
  SELECT DISTINCT obj = 
      QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' 
    + QUOTENAME(OBJECT_NAME(parent_object_id)) 
  FROM sys.foreign_keys
)
SELECT @sql += N'ALTER TABLE ' + obj + ' NOCHECK CONSTRAINT ALL;
' FROM x;

EXEC sp_executesql @sql;

重新启用:

DECLARE @sql NVARCHAR(MAX) = N'';

;WITH x AS 
(
  SELECT DISTINCT obj = 
      QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' 
    + QUOTENAME(OBJECT_NAME(parent_object_id)) 
  FROM sys.foreign_keys
)
SELECT @sql += N'ALTER TABLE ' + obj + ' WITH CHECK CHECK CONSTRAINT ALL;
' FROM x;

EXEC sp_executesql @sql;

但是,您将无法截断表格,您必须以正确的顺序从表格中删除。如果您需要截断它们,则需要完全删除约束,然后重新创建它们。如果您的外键约束都是简单的单列约束,这很容易做到,但如果涉及多列,则肯定会更复杂。

您可以尝试以下方法。为了使它成为 SSIS 包的一部分,您需要在 SSIS 包运行时存储 FK 定义的地方(您将无法在一个脚本中完成所有这些操作)。所以在一些实用程序数据库中,创建一个表:

CREATE TABLE dbo.PostCommand(cmd NVARCHAR(MAX));

然后在您的数据库中,您可以有一个执行此操作的存储过程:

DELETE other_database.dbo.PostCommand;

DECLARE @sql NVARCHAR(MAX) = N'';

SELECT @sql += N'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(fk.parent_object_id))
   + '.' + QUOTENAME(OBJECT_NAME(fk.parent_object_id)) 
   + ' ADD CONSTRAINT ' + fk.name + ' FOREIGN KEY (' 
   + STUFF((SELECT ',' + c.name
    FROM sys.columns AS c 
        INNER JOIN sys.foreign_key_columns AS fkc 
        ON fkc.parent_column_id = c.column_id
        AND fkc.parent_object_id = c.[object_id]
    WHERE fkc.constraint_object_id = fk.[object_id]
    ORDER BY fkc.constraint_column_id 
    FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, '')
+ ') REFERENCES ' + 
QUOTENAME(OBJECT_SCHEMA_NAME(fk.referenced_object_id))
+ '.' + QUOTENAME(OBJECT_NAME(fk.referenced_object_id))
+ '(' + 
STUFF((SELECT ',' + c.name
    FROM sys.columns AS c 
        INNER JOIN sys.foreign_key_columns AS fkc 
        ON fkc.referenced_column_id = c.column_id
        AND fkc.referenced_object_id = c.[object_id]
    WHERE fkc.constraint_object_id = fk.[object_id]
    ORDER BY fkc.constraint_column_id 
    FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, '') + ');
' FROM sys.foreign_keys AS fk
WHERE OBJECTPROPERTY(parent_object_id, 'IsMsShipped') = 0;

INSERT other_database.dbo.PostCommand(cmd) SELECT @sql;

IF @@ROWCOUNT = 1
BEGIN
  SET @sql = N'';

  SELECT @sql += N'ALTER TABLE ' + QUOTENAME(OBJECT_SCHEMA_NAME(fk.parent_object_id))
    + '.' + QUOTENAME(OBJECT_NAME(fk.parent_object_id)) 
    + ' DROP CONSTRAINT ' + fk.name + ';
  ' FROM sys.foreign_keys AS fk;

  EXEC sp_executesql @sql;
END

现在,当您的 SSIS 包完成时,它应该调用一个不同的存储过程,它会:

DECLARE @sql NVARCHAR(MAX);

SELECT @sql = cmd FROM other_database.dbo.PostCommand;

EXEC sp_executesql @sql;

如果您只是为了能够截断而不是删除而执行所有这些操作,我建议您直接执行删除操作。也许使用批量日志恢复模型来最小化日志的影响。总的来说,我看不出这个解决方案会比仅以正确的顺序使用删除快得多。

2014 年,我在这里发表了一篇更详细的文章:

Drop and Re-Create All Foreign Key Constraints in SQL Server

【讨论】:

请查看您是否有任何工作代码,因为我必须截断表格,如果没有,我认为我的身份插入可能会导致问题。 顺便说一句。我试图运行您的脚本以禁用和启用。禁用运行没有问题,但是当我尝试启用时,我得到“消息 547,级别 16,状态 0,第 1 行 ALTER TABLE 语句与 FOREIGN KEY 约束冲突......”的冲突 @HaBo 同时您是否插入了违反约束的数据?如果表中包含不符合约束的数据,则无法启用约束。 @HaBo ...或者您可能有循环引用。这些命令在一个简单的数据库中对我有用。我很难知道你出了什么问题。 @HaBo 试试:SELECT name, is_disabled FROM sys.foreign_keys;这就是为什么您首先在测试系统上运行您不理解的代码示例,这些示例是网站上的陌生人提供给您的。【参考方案2】:

使用内置的 sp_msforeachtable 存储过程。

禁用所有约束:

EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT ALL";

启用所有约束:

EXEC sp_msforeachtable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT ALL";

删除所有表格:

EXEC sp_msforeachtable "DROP TABLE ?";

【讨论】:

本周对此答案投了 2 票;想知道问题出在哪里? 也许有人试图在生产数据库上执行这 3 个查询,尤其是最后一个查询 :-) 遗憾的是,sp_msforeachtable 在 Azure SQL 中不存在 DISABLING 和 DROPPING 约束是两个完全不同的东西。如果禁用它们,您将无法截断表,这就是 SQL Server 的工作方式。您必须删除它们,然后截断表,然后再次添加约束。【参考方案3】:

一个很好的参考在:http://msdn.microsoft.com/en-us/magazine/cc163442.aspx 在“禁用所有外键”部分下

受此启发,可以创建一个临时表并在该表中插入约束,然后删除约束,然后从该临时表中重新应用它们。说够了,这就是我要说的

 SET NOCOUNT ON

    DECLARE @temptable TABLE(
       Id INT PRIMARY KEY IDENTITY(1, 1),
       FKConstraintName VARCHAR(255),
       FKConstraintTableSchema VARCHAR(255),
       FKConstraintTableName VARCHAR(255),
       FKConstraintColumnName VARCHAR(255),
       PKConstraintName VARCHAR(255),
       PKConstraintTableSchema VARCHAR(255),
       PKConstraintTableName VARCHAR(255),
       PKConstraintColumnName VARCHAR(255)    
    )

    INSERT INTO @temptable(FKConstraintName, FKConstraintTableSchema, FKConstraintTableName, FKConstraintColumnName)
    SELECT 
       KeyColumnUsage.CONSTRAINT_NAME, 
       KeyColumnUsage.TABLE_SCHEMA, 
       KeyColumnUsage.TABLE_NAME, 
       KeyColumnUsage.COLUMN_NAME 
    FROM 
       INFORMATION_SCHEMA.KEY_COLUMN_USAGE KeyColumnUsage
          INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TableConstraints
             ON KeyColumnUsage.CONSTRAINT_NAME = TableConstraints.CONSTRAINT_NAME
    WHERE
       TableConstraints.CONSTRAINT_TYPE = 'FOREIGN KEY'

    UPDATE @temptable SET
       PKConstraintName = UNIQUE_CONSTRAINT_NAME
    FROM 
       @temptable tt
          INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS ReferentialConstraint
             ON tt.FKConstraintName = ReferentialConstraint.CONSTRAINT_NAME

    UPDATE @temptable SET
       PKConstraintTableSchema  = TABLE_SCHEMA,
       PKConstraintTableName  = TABLE_NAME
    FROM @temptable tt
       INNER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TableConstraints
          ON tt.PKConstraintName = TableConstraints.CONSTRAINT_NAME

    UPDATE @temptable SET
       PKConstraintColumnName = COLUMN_NAME
    FROM @temptable tt
       INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE KeyColumnUsage
          ON tt.PKConstraintName = KeyColumnUsage.CONSTRAINT_NAME


    --Now to drop constraint:
    SELECT
       '
       ALTER TABLE [' + FKConstraintTableSchema + '].[' + FKConstraintTableName + '] 
       DROP CONSTRAINT ' + FKConstraintName + '

       GO'
    FROM
       @temptable

    --Finally to add constraint:
    SELECT
       '
       ALTER TABLE [' + FKConstraintTableSchema + '].[' + FKConstraintTableName + '] 
       ADD CONSTRAINT ' + FKConstraintName + ' FOREIGN KEY(' + FKConstraintColumnName + ') REFERENCES [' + PKConstraintTableSchema + '].[' + PKConstraintTableName + '](' + PKConstraintColumnName + ')

       GO'
    FROM
       @temptable

    GO

【讨论】:

禁用所有约束,可以试试..... EXEC sp_msforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all" 并启用它们 exec sp_msforeachtable @command1="print '?'", @command2="ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all" 但是这些会禁用所有约束,所以不确定它是否对您的 ssis 有帮助,当我们截断表数据时它可能会有所帮助 我不确定表变量有什么帮助......如果这是由 SSIS 包执行的,最后一步将不再有权访问表变量。这就是我的解决方案使用永久表的原因......即使包失败、被取消、服务器崩溃等,这些约束定义仍然可用。此外,您的脚本有一个非常简单的外键约束视图 - 它不会处理多列外键(例如FOREIGN KEY (a,b) REFERENCES dbo.foo(c,d))。 Aaron Bertand:感谢您的回答。【参考方案4】:

有一个简单的方法。

-- Disable all the constraint in database
EXEC sp_msforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT all'

-- Enable all the constraint in database
EXEC sp_msforeachtable 'ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all'

参考SQL SERVER – Disable All the Foreign Key Constraint in Database – Enable All the Foreign Key Constraint in Database

【讨论】:

【参考方案5】:

禁用所有表约束

ALTER TABLE TableName NOCHECK CONSTRAINT ConstraintName

-- 启用所有表约束

ALTER TABLE TableName CHECK CONSTRAINT ConstraintName

【讨论】:

禁用不足以允许截断。【参考方案6】:

如果您使用的数据库架构与“.dbo”不同,或者您的数据库包含由多个字段组成的 Pk,请不要使用 Carter Medlin 的解决方案,否则会损坏您的数据库!!!

当您使用不同的架构时,试试这个(不要忘记之前备份您的数据库!):

DECLARE @sql AS NVARCHAR(max)=''
select @sql = @sql +
    'ALTER INDEX ALL ON ' + SCHEMA_NAME( t.schema_id) +'.'+ '['+ t.[name] + '] DISABLE;'+CHAR(13)
from  
    sys.tables t
where type='u'

select @sql = @sql +
    'ALTER INDEX ' + i.[name] + ' ON ' + SCHEMA_NAME( t.schema_id) +'.'+'[' + t.[name] + '] REBUILD;'+CHAR(13)
from  
    sys.key_constraints i
join
    sys.tables t on i.parent_object_id=t.object_id
where     i.type='PK'

exec dbo.sp_executesql @sql;
go

在做了一些无 Fk 的动作后,你可以用

切换回来
DECLARE @sql AS NVARCHAR(max)=''
select @sql = @sql +
    'ALTER INDEX ALL ON ' + SCHEMA_NAME( t.schema_id) +'.'+'[' +  t.[name] + '] REBUILD;'+CHAR(13)
from  
    sys.tables t
where type='u'
print @sql

exec dbo.sp_executesql @sql;
exec sp_msforeachtable "ALTER TABLE ? WITH NOCHECK CHECK CONSTRAINT ALL";

【讨论】:

当然炸了所有东西。通常不要尝试这些东西,但有备份。不幸的是,不可能在不完全删除约束的情况下截断表,然后将其添加回来。禁用约束不适用于 MSSQL 截断(使用 SQL2016 及更早版本 - 不确定最新版本)【参考方案7】:

不需要在 sql 上运行对 sidable FK 的查询。如果您有从表 A 到 B 的 FK,您应该:

从表 A 中删除数据 从表 B 中删除数据 在 B 上插入数据 在 A 上插入数据

你也可以告诉目的地不要检查约束

【讨论】:

你不能截断被外键约束引用的表,即使表是空的,即使约束被禁用。你试过了吗? 你是对的,我忘记了那个细节,但你可以运行 delete * from.... 并重置身份 因此,请使用该信息更新您的答案。除非删除外键,否则用户不能使用 truncate。【参考方案8】:

即使您禁用外键,也无法截断表格。所以您可以使用 delete 命令从表中删除所有记录,但请注意是否使用 delete 包含数百万条记录的表的命令,那么你的包会很慢 并且您的事务日志大小会增加,并且可能会填满您宝贵的磁盘空间。

如果你放弃约束,你可能会用不干净的数据填满你的表 当您尝试重新创建约束时,它可能不允许您这样做,因为它会给出错误。 所以请确保如果您放弃约束,您正在加载彼此正确相关并满足您要重新创建的约束关系的数据。

所以请仔细考虑每种方法的优缺点,并根据自己的要求使用

【讨论】:

【参考方案9】:

禁用所有索引(包括 pk,这将禁用所有 fks),然后重新启用 pks。

DECLARE @sql AS NVARCHAR(max)=''
select @sql = @sql +
    'ALTER INDEX ALL ON [' + t.[name] + '] DISABLE;'+CHAR(13)
from  
    sys.tables t
where type='u'

select @sql = @sql +
    'ALTER INDEX ' + i.[name] + ' ON [' + t.[name] + '] REBUILD;'+CHAR(13)
from  
    sys.key_constraints i
join
    sys.tables t on i.parent_object_id=t.object_id
where
    i.type='PK'


exec dbo.sp_executesql @sql;
go

[加载您的数据]

然后让一切恢复生机......

DECLARE @sql AS NVARCHAR(max)=''
select @sql = @sql +
    'ALTER INDEX ALL ON [' + t.[name] + '] REBUILD;'+CHAR(13)
from  
    sys.tables t
where type='u'

exec dbo.sp_executesql @sql;
go

【讨论】:

以上是关于暂时禁用所有外键约束的主要内容,如果未能解决你的问题,请参考以下文章

如何在 MySQL 中临时禁用外键约束?

如何在 MySQL 中临时禁用外键约束?

oracle 删除外键约束 禁用约束 启用约束

批量执行语句之——禁用所有表的外键

Oracle中禁用了外键约束对系统有没有影响

如何在导入过程中禁用Oracle约束条件和触发器