외래 키 제약 조건으로 인해 사이클이나 다중 캐스케이드 경로가 발생할 수 있습니까?


176

테이블에 제약 조건을 추가하려고 할 때 문제가 있습니다. 오류가 발생합니다.

테이블 '직원'에 FOREIGN KEY 제약 조건 'FK74988DB24B3C886'을 도입하면 사이클 또는 여러 계단식 경로가 발생할 수 있습니다. ON DELETE NO ACTION 또는 ON UPDATE NO ACTION을 지정하거나 다른 FOREIGN KEY 제약 조건을 수정하십시오.

내 제약 조건은 Code테이블과 테이블 사이에 employee있습니다. Code테이블에 포함 된 Id, Name, FriendlyName, TypeValue. 는 employee기준 코드 때문에 코드의 각 유형에 대한 참조가있을 수 있다는 것이 다수의 필드를 갖는다.

참조 된 코드가 삭제되면 필드를 null로 설정해야합니다.

내가 어떻게 할 수있는 아이디어가 있습니까?


해결책 중 하나가 여기 있습니다
IsmailS

답변:


180

SQL Server는 캐스케이드 경로의 간단한 계산을 수행하며 실제로주기가 존재하는지 여부를 해결하려고 시도하는 대신 최악의 상황을 가정하고 참조 조치 (CASCADE)를 작성하지 않습니다. 참조 조치 없이도 제한 조건을 작성할 수 있으며 여전히 작성해야합니다. 디자인을 변경할 수 없거나 (그렇게하면 문제가 발생할 수있는 경우) 최후의 수단으로 트리거 사용을 고려해야합니다.

캐스케이드 경로를 해결하는 FWIW는 복잡한 문제입니다. 다른 SQL 제품은 단순히 문제를 무시하고주기를 만들 수 있도록합니다.이 경우 어떤 값을 마지막으로 덮어 쓰는지, 아마도 디자이너의 무지 (예 : ACE / Jet에서 수행)를 보는 경쟁이 될 것입니다. 일부 SQL 제품이 간단한 사례를 해결하려고 시도한다는 것을 알고 있습니다. 사실, SQL Server는 시도하지도 않고 하나 이상의 경로를 허용하지 않아 매우 안전하며 최소한 그렇게 말합니다.

마이크로 소프트 자체는 조언 대신 FK 제약의 트리거의 사용을.


2
내가 여전히 이해할 수없는 것은 트리거를 사용하여이 "문제"를 해결할 수 있다면 트리거가 "사이클 또는 여러 캐스케이드 경로를 유발하지 않는다"는 것입니다.
armen

5
@armen : 트리거가 시스템이 암시 적으로 자체적으로 알아낼 수없는 로직을 명시 적으로 제공하기 때문에 삭제 참조 조치에 대한 다중 경로가있는 경우 트리거 코드는 삭제 된 테이블과 순서를 정의합니다.
언젠가

6
또한 첫 번째 작업이 완료된 후 트리거가 실행되므로 진행중인 레이스가 없습니다.
Bon

2
@ dumbledad : 제약 조건 (조합에 따라)이 작업을 수행 할 수없는 경우에만 트리거를 사용하십시오. 제약은 선언적이며 구현은 시스템의 책임입니다. 트리거는 절차 적 코드이므로 구현을 코딩 (및 디버그)하고 단점 (성능 저하 등)을 견뎌야합니다.
언젠가

1
이것의 문제점은 외래 키 제약 조건을 제거하는 한 트리거가 작동한다는 것입니다. 즉, 데이터베이스 삽입에 대한 참조 무결성 검사가 없으므로 처리를 위해 더 많은 트리거가 필요합니다. 트리거 솔루션은 데이터베이스 설계를 저하시키는 토끼 구멍입니다.
Neutrino

99

여러 개의 캐스 케이 딩 경로가있는 일반적인 상황은 다음과 같습니다. 두 개의 세부 사항이있는 마스터 테이블 "Master"및 "Detail1"및 "Detail2"라고합시다. 두 세부 사항 모두 계단식 삭제입니다. 지금까지 아무런 문제가 없습니다. 그러나 두 세부 사항에 다른 테이블과 일대 다 관계가있는 경우 (예 : "SomeOtherTable") SomeOtherTable에는 Detail1ID 열과 Detail2ID 열이 있습니다.

Master { ID, masterfields }

Detail1 { ID, MasterID, detail1fields }

Detail2 { ID, MasterID, detail2fields }

SomeOtherTable {ID, Detail1ID, Detail2ID, someothertablefields }

즉, SomeOtherTable의 일부 레코드는 Detail1 레코드와 연결되고 SomeOtherTable의 일부 레코드는 Detail2 레코드와 연결됩니다. SomeOtherTable 레코드가 두 Details에 모두 속하지 않는다고 보장 되더라도 Master에서 SomeOtherTable까지의 다중 계단식 경로 (Detail1을 통해 하나와 Detail2를 통해 하나씩)가 있기 때문에 SomeOhterTable의 레코드를 두 가지 세부 정보 모두에 대해 계단식으로 삭제할 수 없습니다. 이제 당신은 이미 이것을 이해했을 것입니다. 가능한 해결책은 다음과 같습니다.

Master { ID, masterfields }

DetailMain { ID, MasterID }

Detail1 { DetailMainID, detail1fields }

Detail2 { DetailMainID, detail2fields }

SomeOtherTable {ID, DetailMainID, someothertablefields }

모든 ID 필드는 키 필드 및 자동 증분입니다. 요점은 세부 사항 테이블의 DetailMainId 필드에 있습니다. 이 필드는 핵심 및 참조 제약 조건입니다. 이제 마스터 레코드 만 삭제하여 모든 항목을 계단식으로 삭제할 수 있습니다. 단점은 각 detail1 레코드와 detail2 레코드마다 DetailMain 레코드 (정확하고 고유 한 ID를 얻기 위해 먼저 작성 됨)도 있어야한다는 것입니다.


1
귀하의 의견은 제가 직면 한 문제를 이해하는 데 많은 도움이되었습니다. 감사합니다! 경로 중 하나에 대해 계단식 삭제를 끄고 다른 방법 (저장 프로 시저; 트리거; 코드 등)의 다른 레코드 삭제를 처리하는 것이 좋습니다. 그러나 나는 같은 문제의 다른 적용 가능성에 대해 귀하의 솔루션 (한 경로로 그룹화)을 염두에두고 있습니다.
freewill

1
crux (및 설명)를 사용하기위한 하나
masterwok

방아쇠를 쓰는 것보다 낫습니까? 계단식 작동을 위해 추가 테이블을 추가하는 것이 이상하게 보입니다.
dumbledad

방아쇠를 쓰는 것보다 낫습니다. 그들의 논리는 불투명하고 다른 것에 비해 비효율적입니다. 세밀한 제어를 위해 큰 테이블을 더 작은 테이블로 나누는 것은 더 나은 표준화 된 데이터베이스의 자연스러운 결과 일뿐입니다.
Neutrino

12

나는 (기능적으로) SCHEMA와 DATA의 사이클 및 / 또는 다중 경로 사이에 큰 차이가 있음을 지적합니다. DATA의주기 및 다중 경로는 처리를 복잡하게하고 성능 문제 ( "적절한"처리 비용)를 유발할 수 있지만 스키마에서 이러한 특성의 비용은 0에 가까워 야합니다.

RDB에서 가장 명백한주기는 계층 구조 (org 차트, 부분, 하위 부분 등)에서 발생하기 때문에 SQL Server가 최악을 가정하는 것은 불행합니다. 즉, 스키마주기 == 데이터주기. 실제로 RI 제약 조건을 사용하는 경우 실제로 데이터에주기를 구축 할 수 없습니다!

다중 경로 문제가 비슷한 것 같습니다. 즉, 스키마의 다중 경로가 반드시 데이터의 다중 경로를 의미하지는 않지만 다중 경로 문제에 대한 경험이 적습니다.

SQL 서버가있는 경우 물론 않았다 여전히 (32)의 깊이 대상이 될 것 사이클을 허용하지만 아마 대부분의 경우에 적합합니다. (그러나 데이터베이스 설정이 아닙니다.

"삭제 대신"트리거도 작동하지 않습니다. 두 번째로 테이블을 방문하면 트리거가 무시됩니다. 따라서 캐스케이드를 실제로 시뮬레이트하려면주기가있는 경우 스토어드 프로 시저를 사용해야합니다. 그러나 Delete-Trigger는 다중 경로의 경우 작동합니다.

Celko는주기를 도입하지 않지만 상충 관계가있는 계층을 나타내는 "더 나은"방법을 제안합니다.


"RI 제약 조건을 사용하는 경우 실제로 데이터에주기를 구축 할 수 없습니다!" -- 좋은 지적!
언젠가

물론 데이터 순환 성을 구축 할 수 있지만 MSSQL에서는 UPDATE 만 사용합니다. 다른 RDBM은 지연 제약 조건을 지원합니다 (삽입 / 업데이트 / 삭제 시점이 아니라 커밋 시점에 무결성이 보장됨).
칼 크릭


3

그 소리에 따라 기존 외래 키 중 하나에 대해 OnDelete / OnUpdate 작업이 수행되어 코드 테이블이 수정됩니다.

이 외래 키를 만들면 주기적 문제가 발생합니다.

예 : 직원 업데이트, 업데이트시 작업에 의해 코드가 변경되고, 업데이트시 작업에 의해 직원이 변경됩니다.

두 테이블에 대한 테이블 정의와 외래 키 / 제약 정의를 게시하면 문제의 위치를 ​​알려줄 수 있습니다.


1
그들은 상당히 길기 때문에 여기에 게시 할 수 있다고 생각하지 않지만 귀하의 도움을 많이 주시면 감사하겠습니다. 내가 당신에게 보낼 수있는 방법이 있는지 모르겠습니까? 그것을 시도하고 설명하십시오 : 존재하는 유일한 제약 조건은 간단한 INT Id 키로 코드를 참조하는 필드가있는 3 개의 테이블에서만 발생합니다. 문제는 Employee에 코드 테이블을 참조하는 여러 필드가 있으며 모두 SET NULL로 계단식으로 연결하려는 것 같습니다. 필요한 것은 코드가 삭제 될 때 코드에 대한 참조가 모든 곳에서 null로 설정되어야한다는 것입니다.

어쨌든 게시하십시오 ... 나는 여기에있는 사람이 마음에 들지 않을 것이라고 생각하며 코드 창은 스크롤 블록에서 올바르게 형식을 지정합니다 :)
Eoin Campbell

2

이는 Emplyee가 다른 단체의 컬렉션을 보유하고있을 수 있기 때문입니다.

public class Employee{
public virtual ICollection<Qualification> Qualifications {get;set;}

}

public class Qualification{

public Employee Employee {get;set;}

public virtual ICollection<University> Universities {get;set;}

}

public class University{

public Qualification Qualification {get;set;}

}

DataContext에서 다음과 같을 수 있습니다

protected override void OnModelCreating(DbModelBuilder modelBuilder){

modelBuilder.Entity<Qualification>().HasRequired(x=> x.Employee).WithMany(e => e.Qualifications);
modelBuilder.Entity<University>.HasRequired(x => x.Qualification).WithMany(e => e.Universities);

}

이 경우 직원에서 자격으로, 자격에서 대학으로 체인이 있습니다. 그래서 나에게도 동일한 예외가 발생했습니다.

내가 바뀌었을 때 그것은 나를 위해 일했다

    modelBuilder.Entity<Qualification>().**HasRequired**(x=> x.Employee).WithMany(e => e.Qualifications); 

    modelBuilder.Entity<Qualification>().**HasOptional**(x=> x.Employee).WithMany(e => e.Qualifications);

1

트리거는이 문제에 대한 해결책입니다.

IF OBJECT_ID('dbo.fktest2', 'U') IS NOT NULL
    drop table fktest2
IF OBJECT_ID('dbo.fktest1', 'U') IS NOT NULL
    drop table fktest1
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'fkTest1Trigger' AND type = 'TR')
    DROP TRIGGER dbo.fkTest1Trigger
go
create table fktest1 (id int primary key, anQId int identity)
go  
    create table fktest2 (id1 int, id2 int, anQId int identity,
        FOREIGN KEY (id1) REFERENCES fktest1 (id)
            ON DELETE CASCADE
            ON UPDATE CASCADE/*,    
        FOREIGN KEY (id2) REFERENCES fktest1 (id) this causes compile error so we have to use triggers
            ON DELETE CASCADE
            ON UPDATE CASCADE*/ 
            )
go

CREATE TRIGGER fkTest1Trigger
ON fkTest1
AFTER INSERT, UPDATE, DELETE
AS
    if @@ROWCOUNT = 0
        return
    set nocount on

    -- This code is replacement for foreign key cascade (auto update of field in destination table when its referenced primary key in source table changes.
    -- Compiler complains only when you use multiple cascased. It throws this compile error:
    -- Rrigger Introducing FOREIGN KEY constraint on table may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, 
    -- or modify other FOREIGN KEY constraints.
    IF ((UPDATE (id) and exists(select 1 from fktest1 A join deleted B on B.anqid = A.anqid where B.id <> A.id)))
    begin       
        update fktest2 set id2 = i.id
            from deleted d
            join fktest2 on d.id = fktest2.id2
            join inserted i on i.anqid = d.anqid        
    end         
    if exists (select 1 from deleted)       
        DELETE one FROM fktest2 one LEFT JOIN fktest1 two ON two.id = one.id2 where two.id is null -- drop all from dest table which are not in source table
GO

insert into fktest1 (id) values (1)
insert into fktest1 (id) values (2)
insert into fktest1 (id) values (3)

insert into fktest2 (id1, id2) values (1,1)
insert into fktest2 (id1, id2) values (2,2)
insert into fktest2 (id1, id2) values (1,3)

select * from fktest1
select * from fktest2

update fktest1 set id=11 where id=1
update fktest1 set id=22 where id=2
update fktest1 set id=33 where id=3
delete from fktest1 where id > 22

select * from fktest1
select * from fktest2

0

데이터베이스 트리거 정책 유형의 오류입니다. 트리거는 코드이며 Cascade Deletion과 같은 Cascade 관계에 지능이나 조건을 추가 할 수 있습니다. CascadeOnDelete 끄기 와 같이 관련 테이블 옵션을 특수화해야 할 수도 있습니다 .

protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
    modelBuilder.Entity<TableName>().HasMany(i => i.Member).WithRequired().WillCascadeOnDelete(false);
}

또는이 기능을 완전히 끄십시오.

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

-2

이 문제에 대한 나의 해결책은 ASP.NET Core 2.0 및 EF Core 2.0을 사용하여 다음을 순서대로 수행하는 것입니다.

  1. update-databasePMC (Package Management Console)에서 명령을 실행 하여 데이터베이스를 만듭니다 (이로 인해 "FOREIGN KEY 제약 조건 소개 중 ... 사이클 또는 여러 계단식 경로가 발생할 수 있습니다."오류가 발생 함)

  2. script-migration -IdempotentPMC에서 명령을 실행 하여 기존 테이블 / 제약에 관계없이 실행할 수있는 스크립트를 작성 하십시오.

  3. 그 결과 스크립트를 가지고 찾기 ON DELETE CASCADE및 교체ON DELETE NO ACTION

  4. 데이터베이스에 대해 수정 된 SQL 실행

이제 마이그레이션이 최신 상태 여야하며 계단식 삭제가 발생하지 않아야합니다.

Entity Framework Core 2.0 에서이 작업을 수행 할 수있는 방법을 찾지 못했습니다.

행운을 빕니다!


이주 파일을 변경하여 (sql 스크립트를 변경하지 않고) 이주 파일을 변경할 수 있습니다. 즉, 이주 파일에서 onDelete 조치를 캐스케이드에서 제한으로 설정
Rushi Soni

유연한 어노테이션을 사용하여이를 지정하는 것이 좋습니다. 따라서 마이그레이션 폴더를 삭제하고 다시 작성하는 경우이를 기억할 필요가 없습니다.
Allen Wang

내 경험에 따르면, 유창한 주석을 사용할 수 있고 사용해야합니다 (내가 사용합니다).하지만 종종 버그가 있습니다. 코드에서 단순히 지정하는 것이 항상 효과가있는 것은 아닙니다.
user1477388
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.