SQL estimates are way off on DELETE statement with Triggers on huge tables

Assuming all the related tables have correct indexing for the delete paths, you could try:

DELETE [Trips]
WHERE [ISAFileName]='ID_774199_20200311_133117.isa'
OPTION (LOOP JOIN, FAST 1, USE HINT ('FORCE_LEGACY_CARDINALITY_ESTIMATION'));

If that works, try to reduce it to the minimal number of hints.

These sorts of plans are very challenging for cardinality estimation, and the 'default' CE model often makes a mess.

Once you have a plan shape that works well, you should be able to force that shape using a plan guide etc. if necessary.


A table scan on cascade delete is a common symptom of not having proper indexes on your tables.

Ensure that all of the FK tables have indexes supporting the foreign keys. IE a clustered or non-clustered index with the FK column(s) as the leading column(s) in the index of the other tables.

EG

create index ix_TripId on EventConditions (TripId)

And consider whether the TripID FK column shouldn't be the leading column in the Clustered Index, eg:

create table EventConditions
(
  TripId int not null,
  EventId bigint not null,
  EventConditionDefId int not null,
  constraint pk_EventConditions 
     primary key clustered(TripId, EventId, EventConditionDefId),
  ...
)

This would optimize each table for access by TripId.

Additionally

One of the thing I was thinking of, was removing the foreign keys on the two biggest table and delete their rows in an instead of trigger.

You don't need to remove the FKs. Just delete from the child tables first, and possibly remove ON DELETE CASCADE from the FK to require deleting the child tables first. Which would start with loading temp tables with the key values to be deleted at each level, and loading them from the top down.

create table #tripIdsToDelete(TripId int primary key)
insert into #tripIdsToDelete ...

create table #EventIdsToDelete(EventId int primary key)
insert into #EventIdsToDelete(EventID)
  select EventId from Events 
  where TripId in (select TripId from #tripIdsToDelete)
...
create table #EventConditionIdsToDelete ...