Як слід обробляти видалення в базі даних?


44

Я хотів би реалізувати функцію "видалити" у веб-додатку, щоб користувач міг змінити свою думку і відновити видалений запис. Думки про те, як це здійснити? Деякі варіанти, які я розглядав, - це фактично видалення запису, про які йдеться, та збереження змін в окремій таблиці аудиту, або не видалення запису та використання булевого "видаленого" стовпця для позначення його як видаленого. Останнє рішення вимагало б додаткової логіки програми для ігнорування "видалених" записів за звичайних обставин, але значно полегшило б реалізацію відновлення записів на стороні програми.


Я забув згадати, що у другому випадку записи, що позначені прапором, потрібно буде видалити або перемістити через певний розумний проміжок часу.
Abie

Яку базу даних ви використовуєте?
Еван Керролл

Тимчасова таблиця - найкраще рішення для SQL Server 2016 і вище.
Самєр

Відповіді:


37

Так, я б точно перейшов до другого варіанту, але я додав би ще одне поле для дати.

Отже, ви додаєте:

delete       boolean
delete_date  timestamp

Це дозволить вам дати час для відшкодування дій.

Якщо час менший за годину, його можна скасувати.

Щоб дійсно видалити запис, видалено, просто створіть збережену процедуру, яка очистить кожну запис із значенням видалення на істинне та час, більший за одну годину, і поставить її як вкладку cron, яка працює кожні 24 години

Година - лише приклад.


Крім того, у вас може бути інший прапор - cleanedабо щось таке, що вказує на те, що дані, пов'язані з цим записом, були належним чином, всебічно видалені. Запис може бути відмінено, якщо cleanedце неправда, і в цьому випадку вона не може бути відновлена .
Гаурав

14
Це спільний підхід. Зазвичай я використовую одне поле, що deleted_atмістить як семантичну deleteбулеву, так і delete_dateчасову позначку. Якщо deleted_atє NULLручка так deleteце FALSEі delete_dateє NULL, deleted_atщо містить тимчасову мітку ручки випадку deleteє TRUEі delete_dateмістить мітку часу, економлячи логіку часу, зберігання і застосування.
Жульєн

1
Мені подобається булеве поле та дата. Залежно від того, як ви реалізуєте логіку видалення, ви навіть можете мати окрему таблицю, яка містить дату та унікальний ключ для запису, який був "видалений". Збережені процедури полегшують це. Він займає додатковий простір на рядок, необхідний до 1 біта проти 8+. Ви також зможете звітувати про видалення в день, не торкаючись таблиці джерел.
AndrewSQL

Примітка: delete - це зарезервоване слово в MySQL.
Джейсон Рікард

Пам’ятайте, що відфільтрований індекс на вашому deletedполі може значно покращити ефективність, коли ви запитуєте про невиділені рядки
Ross

21

У наших програмах ми не дійсно нічого видаляти при а користувачі запиту в будь-якому випадку (наші клієнти знаходяться в регульованих середовищах , де видалення нічого потенційно може призвести до правових питань).

Ми зберігаємо старіші версії в окремій таблиці аудиту (так для таблиці some_table, де також є таблиця під назвою some_table_audit), яка ідентична, крім того, що має додатковий ідентифікатор версії (часова мітка, якщо ваша БД підтримує значення часу досить деталізованими, цілим номером версії або UUID, який є зовнішнім ключем до загальної таблиці аудиту тощо), і оновити таблицю аудиту автоматично за допомогою тригера (тому нам не потрібно робити весь код, який оновлює записи, обізнаний про потребу аудиту).

Сюди:

  • операція видалення - це просто просте видалення - не потрібно додавати до цього зайвого коду (хоча ви, можливо, захочете записати, хто вимагав видалити рядки, навіть якщо вони фактично не видалені)
  • вставки та оновлення аналогічно прості
  • ви можете виконати відновлення або відновити, просто повернувши "звичайний" рядок до старої версії (тригер аудиту знову запуститься, тому таблиця слідів аудиту відображатиме і цю зміну)
  • ви можете запропонувати можливість переглянути або повернутись до будь-якої минулої версії, а не лише відновити останню
  • вам не потрібно додавати "позначено як видалене?" перевіряє кожну точку коду, що посилається на відповідну таблицю, або логіку "оновлення копії аудиту" до кожної точки коду, яка видаляє / оновлює рядки (хоча вам потрібно вирішити, що робити зі видаленими рядками в таблиці аудиту: у нас є видалено / не встановлено прапор для кожної версії, щоб не було дір в історії, якщо записи видалено та пізніше відновлено)
  • зберігання копій аудиту в окремій таблиці означає, що ви можете легко розділити їх на різні групи файлів.

Якщо ви використовуєте часову позначку замість (або також) цілого номера версії, ви можете використовувати її для видалення старих копій через встановлений час, якщо потрібно. Але дисковий простір порівняно дешевий в ці дні, тому, якщо у нас немає підстав скидати старі дані (тобто норми щодо захисту даних, які говорять, що ви повинні видаляти клієнтські дані через X місяців / років), ми б не стали.


Ця відповідь існує вже декілька років, і відтоді змінилося кілька ключових речей, які можуть вплинути на таке планування. Я не буду заглиблюватись у великі деталі, але підуть на користь людям, які сьогодні це читають:

  • SQL Server 2016 представив "тимчасові таблиці з системною версією", які роблять для вас багато цієї роботи, і більше того, як надається якийсь симпатичний синтаксичний цукор для спрощення побудови та обслуговування історичних запитів, і вони координують підмножину змін схем між бази та таблиці історії. Вони не без своїх застережень, але вони є потужним інструментом для подібних цілей. Подібні функції є і в інших системах БД.

  • Зміни в законодавстві про захист даних, зокрема, запровадження GDPR, можуть суттєво змінити питання про те, коли дані слід жорстко видалити. Ви повинні зважити залишок не видалення даних, які можуть бути корисними (або, дійсно, юридично необхідними) для аудиторських цілей у більш пізні терміни, не вимагаючи дотримання прав людей (як правило, так і як конкретно визначено у відповідному законодавстві) при розгляді ваші проекти. Це може бути проблемою з тимчасовими таблицями з версією системи, оскільки ви не можете змінювати історію, щоб очистити персональні дані без короткострокових змін схеми, щоб вимкнути відстеження історії під час внесення змін.


Як ви ставитеся до видалення та перейменування стовпців? Встановити все на нуль?
Штійн

1
@Stijn: Нечасто структури змінюються так, що не надто багато. Колумби, як правило, ніколи не видаляються, коли вони існували у виробництві - якщо вони припиняють використовувати, просто скасуйте будь-які обмеження, які зупинять їх, коли вони можуть бути NULL (або додавати за замовчуванням, щоб боротися з обмеженнями, використовуючи "магічне значення", хоча це відчуває себе більш брудним) і перестати посилатися на них в іншому коді. Для перейменувань: додайте нове, перестаньте використовувати старі та при необхідності скопіюйте дані зі старих у нові. Якщо ви перейменовуєте стовпчики, просто переконайтеся, що однакові зміни внесені одночасно до базових та аудиторських таблиць.
Девід Спіллетт

9

З булевим видаленим стовпцем у вас почнуть виникати проблеми, якщо ваша таблиця почне рости і стає дійсно великою. Я пропоную вам переміщувати видалені стовпці раз на тиждень (більш-менш залежно від ваших специфікацій) до іншої таблиці. Таким чином у вас є приємний невеликий активний стіл і великий, який містить усі записи, зібрані за час.


7

Я б пішов з окремим столом. У Ruby on Rails є acts_as_versionedплагін, який в основному зберігає рядок до іншої таблиці з постфіксом _versionперед її оновленням. Хоча вам не потрібна така точна поведінка, вона також повинна працювати для вашої справи (копіюйте перед видаленням).

Як і @Spredzy, я також рекомендую додати delete_dateстовпець, щоб мати змогу програмно очищати записи, які не були відновлені після X годин / днів / що завгодно.


4

Рішення, яке ми використовуємо внутрішньо для цього питання, - це мати стовпчик статусу з деякими твердими кодованими значеннями для деяких конкретних станів об’єкта: Видалено, Активно, Неактивно, Відкрито, Закрито, Заблоковано - кожен статус з певним значенням, що використовується в додатку. З точки зору db ми не видаляємо об'єкти, ми просто змінюємо стан і зберігаємо історію для кожної зміни в таблиці об'єктів.


3

Коли ви говорите, що "Останнє рішення потребує додаткової логіки програми, щоб ігнорувати" видалені "записи", простим рішенням є перегляд, який їх фільтрує.


Це не лише питання погляду. Будь-які операції, що виконуються на наборі, повинні були виключати "видалені" записи.
Ебі

2

Подібно до того, що запропонував Spredzy, ми використовуємо поле часової позначки для видалення у всіх наших програмах. Булева буває зайвою, оскільки встановлена ​​часова марка вказує на те, що запис видалено. Таким чином, наш PDO завжди додає AND (deleted IS NULL OR deleted = 0)до вибраних операторів, якщо модель явно не вимагає включення видалених записів.

Наразі ми не збираємо сміття на будь-яких, крім таблиць, які містять краплі або тексти; простір є тривіальним, якщо записи добре нормалізовані, а індексація deletedполя робить обмежений вплив на швидкість вибору.


0

Ви можете також накласти на користувачів (і розробників) наступні дії: "Ви впевнені?", "Ви впевнені?" і "Ви абсолютно, добре і справді впевнені?" запитання до видалення запису. Злегка вибагливий, але варто задуматися.


0

Я звик бачити рядки таблиць із стовпцями, як-от "Видалений дат" у них, і мені це не подобається. Саме поняття "видалено" полягає в тому, що запис не слід було робити в першу чергу. Практично, їх неможливо видалити з бази даних, але я не хочу їх використовувати у своїх гарячих даних. Логічно видалені рядки, за визначенням, є холодними даними, якщо хтось спеціально не хоче бачити видалені дані.

Крім того, кожен запит, який пишеться, повинен спеціально їх виключати, а індекси також повинні враховувати їх.

Мені хотілося б побачити зміни на рівні архітектури бази даних та рівні програми: створити схему під назвою "видалено". Кожна визначена користувачем таблиця має ідентичний еквівалент у схемі "видалених" з додатковими полями, що містять метадані - користувачем, який її видалив і коли. Зовнішні ключі потрібно створити.

Далі видалення стає вставкою-видаленням. Перший рядок, який потрібно видалити, вставляється в його "видалений" аналог схеми. Рядок, про який йдеться в головній таблиці, можна видалити. Однак зайву логіку потрібно додати десь уздовж лінії. Порушення закордонних ключів можна вирішити.

Іноземні ключі повинні бути належним чином оброблені. Неправильно практично видаляти рядок, логічно видалений, але основний / унікальний має стовпці в інших таблицях, які посилаються на нього. Це ніяк не повинно відбуватися. Звичайне завдання може видаляти вдові ряди (рядки, в первинних ключах яких немає посилань в інших таблицях, незважаючи на наявність зовнішнього ключа. Це, однак, ділова логіка.

Загальна користь - це зменшення метаданих у таблиці та підвищення продуктивності. У стовпці "deleteDate" йдеться про те, що насправді цього рядка не повинно бути тут, але для зручності ми залишаємо його там і дозволяємо SQL-запиту обробляти його. Якщо копія видаленого рядка зберігається у схемі "видалених", то основна таблиця із гарячими даними має більший відсоток гарячих даних (при умові, що вони своєчасно архівуються) та меншу кількість непотрібних стовпців метаданих. Індекси та запити більше не потрібно розглядати це поле. Чим коротший розмір рядка, тим більше рядків можна розмістити на сторінці, тим швидше може працювати SQL Server.

Основним недоліком є ​​розмір операції. Зараз замість однієї є дві операції, а також додаткова логіка та помилки. Це може призвести до більшого блокування, ніж оновлення одного стовпця в іншому випадку. У транзакції довше тримаються блокування на столі, і тут задіяні дві таблиці. Видалення виробничих даних, принаймні з мого досвіду, щось рідко робиться. Ще в одній з основних таблиць 7,5% з майже 100 мільйонів записів містять запис у стовпці "Видалено".

Як відповідь на запитання, додаток повинен знати про "відновлення". Просто потрібно було б зробити те ж саме у зворотному порядку: вставити рядок із схеми "видалено" у головну таблицю, а потім видалити рядок із "видаленої схеми". Знову необхідне додаткове оброблення логіки та помилок, щоб уникнути помилок, проблем із сторонніми ключами тощо.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.