Обробка видалених користувачів - окрема чи однакова таблиця?


19

Сценарій полягає в тому, що у мене з’являється все більший набір користувачів, і з часом користувачі скасують свої акаунти, які ми в даний час позначаємо як «видалені» (з прапором) у тій самій таблиці.

Якщо користувачі з однаковою електронною адресою (саме так користувачі входять у систему) бажають створити новий обліковий запис, вони можуть знову зареєструватися, але НОВИЙ акаунт буде створений. (У нас є унікальні ідентифікатори для кожного облікового запису, тому адреси електронної пошти можна дублювати серед живих та видалених).

Що я помітив, це те, що у всій нашій системі ми постійно запитуємо таблицю користувачів, перевіряючи, чи не видаляється користувач, тоді як я думаю, що нам взагалі цього не потрібно робити ... ! [Пояснення1: "постійно запитуючи", я мав на увазі, що у нас є запити, схожі на: "... ВІД користувачів, ЩО isdeleted =" 0 "І ...". Наприклад, нам може знадобитися знайти всіх користувачів, зареєстрованих на всі зустрічі в певну дату, тож у ТОМУ запиті ми також ВІД користувачів, ЩО isdeleted = "0" - це робить мою точку зрозумілішою?]

(1) continue keeping deleted users in the 'main' users table
(2) keep deleted users in a separate table (mostly required for historical
    book-keeping)

Які плюси і мінуси будь-якого підходу?


З яких причин ви тримаєте користувачів?
keppla

2
Це називається soft-delete. Дивіться також Видалення записів бази даних unpermenantley (soft-delete)
Sjoerd,

@keppla - він згадує, що: "історичне ведення книжок".
ChrisF

@ChrisF: мене зацікавила сфера: чи хоче він зберігати книги лише користувачів, чи є ще якісь дані, додані (коментарі до електронної
пошти

Це може допомогти перестати думати про них , як видаляються (не вірно) , і почати думати про свій рахунок скасованих (що є правдою).
Майк Шеррілл 'Відкликання котів'

Відповіді:


13

(1) продовжуйте зберігати видалених користувачів у таблиці "головних" користувачів

  • Плюси: простіші запити у всіх випадках
  • Мінуси: може знизити продуктивність з часом, якщо кількість користувачів є великою

(2) зберігати видалених користувачів в окремій таблиці (в основному це потрібно для історичного обліку)

Ви можете використовувати, наприклад, тригер для автоматичного переміщення видалених користувачів до таблиці історії.

  • Плюси: більш просте обслуговування для активної таблиці користувачів, стабільна продуктивність
  • Мінуси: потрібні різні запити до таблиці історії; однак, оскільки більшість додатків не повинні бути зацікавлені в цьому, цей негативний ефект, ймовірно, обмежений

11
Таблиця розділів (на IsDeleted) видалить проблеми з продуктивністю за допомогою однієї таблиці.
Ян

1
@Ian, якщо кожен запит не надається IsDeleted як критерії запиту (що, як видається, не в первинному питанні), розділення може навіть призвести до погіршення продуктивності.
Адріан Шум

1
@ Адріан, я припускав, що найпоширеніші запити будуть під час входу в систему і що тільки жодним видаленим користувачам не дозволять входити в систему.
Ian

1
Використовуйте індексований вигляд на isdeleted, якщо він стає проблемою продуктивності та ви хочете отримати перевагу від однієї таблиці.
JeffO

10

Настійно рекомендую використовувати ту саму таблицю. Основна причина - цілісність даних. Швидше за все, буде багато таблиць із стосунками залежно від користувачів. Коли користувач видаляється, ви не хочете залишати ці записи сиротами.
Наявність осиротілих записів і ускладнює виконання обмежень, і ускладнює пошук історичної інформації. Інша поведінка, яку слід враховувати, якщо користувач надає використаний електронний лист, якщо ви хочете, щоб вони відновили всі свої старі записи. Це діятиме автоматично, використовуючи м'яке видалення. Що стосується кодування, наприклад, у моєму поточному додатку c # linq, то в кінці всіх запитів автоматично додається стаття, де видалено = 0.


7

"Що я помітив, це те, що у всій нашій системі ми в звичайному процесі речей постійно запитуємо таблицю користувачів, перевіряючи, чи не видаляється користувач".

Це дає мені поганий запах дизайну. Вам слід приховувати таку логіку. Наприклад, ви повинні мати UserServiceметод надання " isValidUser(userId)для використання у всій системі", а не робити щось на зразок:

"отримати запис користувача, перевірте, чи користувач позначений як видалений".

Ваш спосіб зберігання видаленого користувача не повинен впливати на бізнес-логіку.

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

Що слід враховувати:

  • Як довго має бути видалена запис фактично очищена?
  • Яка частка видалених записів?
  • Чи виникне проблема щодо референтної цілісності (наприклад, користувач пересилається з іншої таблиці), якщо ви його фактично видалите з таблиці?
  • Чи плануєте ви знову відкрити користувача?

Зазвичай я б взяв комбінований спосіб:

  1. Позначте запис як видалений (щоб зберегти його для функціональних вимог, наприклад, повторне відкриття змінного струму чи перевірка нещодавно закритого змінного струму).
  2. Після заздалегідь визначеного періоду перемістіть видалений запис в архівну таблицю (з метою бухгалтерського обліку).
  3. Почистіть його після певного заздалегідь визначеного архівного періоду.

1
[Пояснення1: "постійно запитуючи", я мав на увазі, що у нас є запити, схожі на: "... ВІД користувачів, ЩО isdeleted =" 0 "І ...". Наприклад, нам може знадобитися знайти всіх користувачів, зареєстрованих на всі зустрічі в певну дату, тож у ТОМУ запиті ми також ВІД користувачів, ЩО isdeleted = "0" - це робить мою точку яснішою?] @Adrian
Alan Beats

Так набагато зрозуміліше. :) Якщо я це роблю, я вважаю за краще змінити статус користувача, замість того, щоб виглядати як фізичне / логічне видалення. Хоча кількість коду не зменшиться ("і isDeleted = '0'" vs 'та "state <>' TERMINATED '"), але все буде виглядати набагато розумніше, і нормально мати і інший стан користувача. Періодична чистка ТЕРМІНОВАНИХ користувачів також може бути виконана, як було запропоновано в моїй попередній відповіді)
Адріан Шум

5

Щоб правильно відповісти на це питання, спершу потрібно вирішити: Що означає "видалити" в контексті цієї системи / програми?

Щоб відповісти на це запитання, потрібно відповісти на ще одне питання: Чому записи видаляються?

Існує ряд вагомих причин, чому користувачеві може знадобитися видалити дані. Зазвичай я вважаю, що існує точно одна причина (за таблицею), чому видалення може бути необхідним. Деякі приклади:

  • Повернути дисковий простір;
  • Необхідне жорстке видалення відповідно до політики збереження / конфіденційності;
  • Пошкоджені / безнадійно неправильні дані, простіше видалити та відновити, ніж відновити.
  • Більшість рядків будуть видалені, наприклад, журнал таблиці обмежені 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)- там дуже мало шукають вводу / виводу. Але якщо це коли-небудь питання, є способи вирішити це, використовуючи тригери, індексовані / матеріалізовані перегляди, події на рівні додатків тощо.

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


1

Зараз я працюю з системою, де кожна таблиця містить прапор Видаленого для м'якого видалення. Це відмінність усього існування. Він повністю порушує цілісність реляції, коли користувач може "видалити" запис з однієї таблиці, але діти записують, який ФК назад до цієї таблиці не каскадно видаляється. Дійсно для даних про сміття після проходження часу.

Тож рекомендую окремі таблиці історії.


Безумовно, без каскадних зрушень історії у вас точно така ж проблема?
гленатрон

Не у ваших активних таблицях записів, ні.
Джессі К. Слікер

Отже, що відбувається з дочірніми записами, які ФК знімають таблицю користувача після того, як користувач був переданий до таблиці історії?
гленатрон

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

3
Залежить від того, що насправді означає ваше м'яке видалення. Якщо це лише спосіб їх деактивації, не потрібно коригувати записи, пов’язані з деактивованим обліковим записом. Мені це здається просто даними. І так, я маю це впоратися також і в системі, яку я не проектував. Це не означає, що вам це сподобається.
JeffO

1

Розбити стіл надвоє було б найнижчою річчю, яку можна уявити.

Ось два дуже простих кроки, які я рекомендував би:

  1. Перейменуйте таблицю "користувачів" на "всекористувачі".
  2. Створіть представлення під назвою "користувачів" як "виберіть * з усіхкористувачів, де видалено = помилково".

PS Вибачте за кількамісячну затримку відповідей!


0

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

Однак, коли ви створюєте нові облікові записи, тоді, ймовірно, буде простіше перемістити видалені рахунки в окрему таблицю. Жива система не потребує цієї інформації, тому не розкривайте її. Як ви кажете, це робить запити більш простими та, можливо, швидшими на великих наборах даних. Простіший код також простіший в обслуговуванні.


0

Ви не згадуєте про використання СУБД. Якщо у вас є Oracle з належною ліцензією, ви можете розглянути таблицю користувачів на два розділи: активних та видалених користувачів.


Потім ви повинні переміщувати рядки з одного розділу в інший при видаленні користувачів, що, безумовно, не є тим, як розділи призначені для використання.
Péter Török

@ Петер: А? Ви можете розділити всі необхідні критерії, включаючи видалений прапор.
Aaronaught

@Aaronaught, добре, я неправильно сформулював це. СУБД може виконати роботу за вас, але це все-таки додаткова робота (оскільки рядок необхідно фізично перемістити з одного місця в інше, можливо, в інший файл), і це може погіршити фізичний розподіл даних.
Péter Török
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.