Soft-deleting change-tracked user content
Alright, so my solution is to cascade soft-deletions in the after update triggers. Here's an example:

create trigger `tr_user_content_au` after update
    on `user_content` for each row
begin
    if new.`deleted_at` &lt;&gt; null then
        -- The row has been soft-deleted. We assume that no
        -- other fields have been modified.

        -- Soft-delete related rows.
        update `user_content_derived` set `deleted_at` = now();
    else if
        -- Check if any other fields have been modified.
        old.`col_1` &lt;&gt; new.`col_1` or
        old.`col_2` &lt;&gt; new.`col_2` then
        -- Copy modified fields to the change table.
        insert into `user_content_change` (
            `user_content_id`,
            `col_1`,
            `col_2`
        )
        values (
            new.`id`,
            if (old.`col_1` = new.`col_1`, null, old.`col_1`),
            if (old.`col_2` = new.`col_2`, null, old.`col_2`)
        );
    end if;
end;


Also, I use views to filter out already deleted rows:

create view `vw_user_content_visible` as
    select
        `col_1`,
        `col_2`
      from `user_content`
     where `deleted_at` = null;


And finally, I turn even the simplest insert, select or update statements into stored procedures. That's how I mask whether an entry is physically deleted or just soft-deleted. Apart from this, I suppose that people working on application code are less likely to forget about soft-deletions if they are forced to look into the database code to alter queries.