Щоб правильно відповісти на це питання, спершу потрібно вирішити: Що означає "видалити" в контексті цієї системи / програми?
Щоб відповісти на це запитання, потрібно відповісти на ще одне питання: Чому записи видаляються?
Існує ряд вагомих причин, чому користувачеві може знадобитися видалити дані. Зазвичай я вважаю, що існує точно одна причина (за таблицею), чому видалення може бути необхідним. Деякі приклади:
- Повернути дисковий простір;
- Необхідне жорстке видалення відповідно до політики збереження / конфіденційності;
- Пошкоджені / безнадійно неправильні дані, простіше видалити та відновити, ніж відновити.
- Більшість рядків будуть видалені, наприклад, журнал таблиці обмежені X записів / днів.
Є також дуже погані причини жорсткого видалення (докладніше про причини цього):
- Виправити незначну помилку. Зазвичай це підкреслює лінь розробника та ворожий інтерфейс користувача.
- "Анулювати" транзакцію (наприклад, рахунок, який ніколи не повинен був виставляти рахунок).
- Тому що ти можеш .
Чому, запитаєте ви, справді така велика справа? Що з добрим оле ' DELETE
?
- У будь-якій системі, навіть віддалено прив'язаній до грошей, жорстке видалення порушує всілякі очікування бухгалтерського обліку, навіть якщо їх перенести в архівну / надгробну таблицю. Правильний спосіб впоратися з цим є зворотною подією .
- Архівні таблиці мають тенденцію відходити від живої схеми. Якщо ви забудете навіть про один щойно доданий стовпець або каскад, ви просто назавжди втратили ці дані.
- Жорстке видалення може бути дуже дорогою операцією, особливо з каскадами . Багато людей не розуміють, що каскадування більш ніж одного рівня (або в деяких випадках будь-який каскад, залежно від СУБД) призведе до операцій рівня запису замість заданих операцій.
- Повторне, часте жорстке видалення прискорює процес фрагментації індексу.
Отже, м'яке видалення краще, правда? Ні, не дуже:
- Встановити каскади стає вкрай складно. Ви майже завжди опиняєтесь тим, що видається клієнтові як сирота.
- Ви можете відстежувати лише одне видалення. Що робити, якщо рядок буде видалено і повторно відмінено?
- Ефективність читання страждає, хоча це може бути дещо пом’якшене за допомогою розділення, перегляду та / або відфільтрованих індексів.
- Як натякали раніше, у деяких сценаріях / юрисдикціях це може бути незаконним.
Правда полягає в тому, що обидва ці підходи помилкові. Видалення неправильно. Якщо ви справді задаєте це питання, це означає, що ви моделюєте поточний стан замість транзакцій. Це погана, погана практика в базі даних.
Уді Дахан написав про це в " Не видаляй - просто не треба" . Існує завжди якийсь - то завдання, угоди, активність , або (мій кращий термін) подія , яка на самому ділі є «Видалити». Добре, якщо згодом ви хочете денормалізувати таблицю "поточного стану" для продуктивності, але зробіть це після того, як ви прибили модель трансакції, не раніше.
У цьому випадку у вас є "користувачі". Користувачі по суті є клієнтами. Клієнти мають ділові стосунки з вами. Ці стосунки не просто зникають, але вони скасували свій рахунок. Що насправді відбувається:
- Клієнт створює рахунок
- Клієнт скасовує рахунок
- Клієнт поновлює рахунок
- Клієнт скасовує рахунок
- ...
У кожному випадку це той самий клієнт і, можливо, той самий рахунок (тобто поновлення кожного акаунта - це нова угода про обслуговування). То чому ви видаляєте рядки? Це дуже легко моделювати:
+-----------+ +-------------+ +-----------------+
| Account | --->* | Agreement | --->* | AgreementStatus |
+-----------+ +-------------+ +----------------+
| Id | | Id | | AgreementId |
| Name | | AccountId | | EffectiveDate |
| Email | | ... | | StatusCode |
+-----------+ +-------------+ +-----------------+
Це воно. Це все, що там є. Ніколи нічого не потрібно видаляти. Наведене є досить поширеною конструкцією, яка забезпечує хороший ступінь гнучкості, але ви можете її трохи спростити; ви можете вирішити, що вам не потрібен рівень "Угоди" і просто "Обліковий запис" перейти до таблиці "AccountStatus".
Якщо у вашій заяві часто виникає потреба отримати список активних угод / облікових записів, то це (злегка) складний запит, але для цього потрібні перегляди:
CREATE VIEW ActiveAgreements AS
SELECT agg.Id, agg.AccountId, acc.Name, acc.Email, s.EffectiveDate, ...
FROM AgreementStatus s
INNER JOIN Agreement agg
ON agg.Id = s.AgreementId
INNER JOIN Account acc
ON acc.Id = agg.AccountId
WHERE s.StatusCode = 'ACTIVE'
AND NOT EXISTS
(
SELECT 1
FROM AgreementStatus so
WHERE so.AgreementId = s.AgreementId
AND so.EffectiveDate > s.EffectiveDate
)
І ви закінчили. Тепер у вас є щось із усіма перевагами програмного видалення, але жодного з недоліків:
- Осиротілі записи - це не питання, оскільки всі записи видно в усі часи; ви просто вибираєте інший вигляд, коли це необхідно.
- "Видалення" - це зазвичай неймовірно дешева операція - просто вставити один рядок у таблицю подій.
- Ніколи не існує жодного шансу втратити будь-яку історію, ніколи , як би сильно не викручували.
- Ви все ще можете важко видалити обліковий запис, якщо вам потрібно (наприклад, з міркувань конфіденційності), і вам буде зручно знати, що видалення буде чистим і не заважатиме жодній іншій частині програми / бази даних.
Єдине питання, що залишилося вирішити, - це питання ефективності. У багатьох випадках це фактично не виходить через кластерний індекс на AgreementStatus (AgreementId, EffectiveDate)
- там дуже мало шукають вводу / виводу. Але якщо це коли-небудь питання, є способи вирішити це, використовуючи тригери, індексовані / матеріалізовані перегляди, події на рівні додатків тощо.
Не хвилюйтеся про продуктивність занадто рано - важливіше правильно підібрати дизайн, а "правильне" в цьому випадку означає використання бази даних способом використання бази даних як транзакційної системи.