Очередной раз, когда из каждой дырки все кричат "реляции в БД это маст хэв", а на реальной практике понимаешь, что от этого намного больше проблем, чем пользы
Сразу уточню, я говоря про реляционную консистентность (relational constraint) и самым ярким представителем является FOREIGN KEY ... ON DELETE CASCADE
Тут много тонких и философских поинтов, но расскажу о самых насущных:
Каскады непредсказуемы
Когда ты в коде написал db.xxx.delete() используя констрейнты ты абсолютно не знаешь сколько данных в реальности потрутся. Да, можно пойти в БД и визуализировать дерево каскадов, но (1) есть большая вероятность, что код уставшего человека / код ревьюера спокойно дойдет до прода и удалит половину данных или (2) у вас достаточно много таблиц, чтобы не понимать что реально произойдет
Забыл про GC, а он про тебя не забыл
db.xxx.delete() -> каскадом удалились сотни тысяч, связанных с xxx данных -> в БД запуститься Garbage Collector -> все таблицы лочаться и все ждут пока он закончит работу -> вы в говне
Слитый перформанс
Проверка констрейнтов на INSERT и большинистве UPDATE может сжирать до 30-50% времени операции
Как быть?
. Не используйте каскады . Если вам нужно, чтобы при удалении каких-то данных в моменте потерлись другие, то прямо там и пишите весь код удаления . Ставьте кроны, которые в моменты низкой нагрузки системы собирают и сразу большими пачками удаляют данные, оставшиеся после удаления основной сущности . Используйте soft-delete . Если ну очень нужно, то делайте ON DELETE SET NULL
Из приятных сайд-эффектов, научившись жить без реляций перед вами открывается мир множества распределенных баз данных
А это неопасно?
Объявив ON DELETE CASCADE сущность, на которую мы завязываемся, становится родительской и мы от нее зависим
Одновременно с этим, есть высокая вероятность, что когда мы запрашиваем данные в нашем приложении, то запрос начинается с получения родительской сущности, а если ее нет, то и связанные данные не будут попадать в результаты запроса, а значит не будут нам мешать
Опять же, это высокая вероятность, но не гарантия, поэтому вам стоит всегда держать в голове: "Мы не используем contraint, поэтому данные, которые я запрашиваю могут быть нерелевантны, надо убедиться, что это так"
P.S.
Кстати вот эти "оставленные" данные после удаления родительских называется "orphan data" (данные-сироты)
A
Alexandr DP Kuzmichev
2024-06-08 09:40
И чё делать теперь? Как быть?
🦾
🦾 IT-Качалка Давида Шекунца 💪
2024-06-08 09:45
Собственно, так же, как это делают в базах без реляций (Mongo, Scylla, Cassandra, YDB, etc.), то есть, именно то, что я описал в главе "как быть?")
Самый условно "простой", хоть и заставляющий писать более сложные запросы - soft-delete
Но и просто оставление данных с дальнейшей очисткой кроном тоже чаще всего работает
Я бы ещё сказал что если вы уверены что данных, к которым вы делаете реляций немного (до 100, до N mb, или вообще 1-1), то их можно использовать, но опять же, аккуратно
🕸
🕸Денис Лёвкин
2024-06-08 10:08
Чтобы не нарваться на каскадное удаление правильнее ставить не on delete set null, а on delete no action. Зависит от базы. Ссылочная целостность предназначена для обеспечения этой самой целостности. И простого логического удаления вполне достаточно, без необходимости отказаться от ссылочной целостности.
🦾
🦾 IT-Качалка Давида Шекунца 💪
2024-06-08 11:20
На тему SET NULL - удобство в том, что в типизированных языках видно, что поле может быть NULL, что является очень удобной подсказкой, что связь могла быть потеряна + легко вычислять orphan data, потому что нужно просто вычищать только те, у которых значение NULL
На тему ссылочной консистентности - основной кейс ее применения (который сейчас приходить мне в голову): "не сделать insert / update заменив ссылку на несуществующую"
Но тут есть логическая коллизия:
1. Если мы делаем no action, тогда предполагаем, что у нас будут ссылки на несуществующие объекты, тогда нет никакой проблемы добавлять новые записи с несуществующими ссылками
2. Если же нам нужно 100% убедиться, что данные существуют (например, для последующих операций), то мы не можем положиться на базу, потому в следующую мс после insert с проверкой ссылочной консистентности, кто-то удаляет родительскую сущность и эта проверка ничего не дала
3. Единственный вариант, это делать транзакцию минимум с изоляцией Repeatable Read, в которой осуществить чтение, запись и все дальнейшие операции, но для этого no action не нужен
Поэтому, no action имеет меньшую ценность, чем set null
Или же я не учитываю ещё кейсы
🕸
🕸Денис Лёвкин
2024-06-08 12:37
Ещё как вариант - недопустить удаления используемой, например есть товар ( prodid, prodname, unitid) и его атрибут единица измерения. При удалении единицы измерения при set null мы получим полное безобразие в данных и ещё и данные о единице измерения потеряем. При no action движок бд просто не даст вам допустить такую ошибку. Уж потрудитесь сами принять правильное действие)))
🦾
🦾 IT-Качалка Давида Шекунца 💪
2024-06-08 12:38
Да, ок, это крутой кейс, тут согласен
I
Ivan Zhuravlev
2024-06-08 14:05
Ну уже всё сказали, добавлю лишь, что в этом выборе не стоит забывать про упомянутый soft delete, он тоже играет значительную роль в архитектуре БД
G
Grigoriy ИТК Malyshev
2024-06-13 07:55
hbd!
V
Vitalii Borisov
2024-06-13 17:08
>Если ну очень нужно, то делайте ON DELETE SET NULL
апдейт это ведь delete + insert? т.е. возникает эта же проблема: каскадом удалились сотни тысяч, связанных с xxx данных -> в БД запуститься Garbage Collector -> все таблицы лочаться и все ждут пока он закончит работу -> вы в говне
🦾
🦾 IT-Качалка Давида Шекунца 💪
2024-06-14 06:19
Пасиба ❤️
🦾
🦾 IT-Качалка Давида Шекунца 💪
2024-06-14 06:23
Зависит от БД:
– в PG это будет MVCC insert с будущей чисткой предыдущих версий, да, менее оптимально, чем NO ACTION, но глобального лока и больших проблем с GC тут не будет (но да, он все равно в один момент должен будет пройтись и почистить все предыдущие записи, это правда)
– В MySQL будет inplace update + запись в rollback journal, тоже менее оптимально, чем NO ACTION, но опять же без глобального лока и проблем с GC
Большинство остальных реляционных баз исповедуют примерно теже принципы, НО если реально где-то ситуацию delete + insert, то да, тогда ON DELETE SET NULL создаст проблемы