Физическое и логическое (мягкое) удаление данных


Физическое удаление данных подразумевает под собой полное стирание объекта (объектов) из хранилища.

Логическое (мягкое) удаление реализуется установкой специальной пометки, но без полного стирания объекта из хранилища. В файловой системе аналогом мягкого удаления является каталог корзины. Объект файловой системы перемещается в каталог корзины, из которой объект легко может быть извлечён и восстановлен. В базе данных два пути:

  • добавление специального поля deleted типа Boolean;
  • добавление отдельной таблицы удаляемых объектов.

Добавление поля проще. Но всё же стоит рассмотреть второй вариант, хоть он и влечёт за собой добавление дополнительной таблицы и усложнение кода. 

Основные преимущества мягкого удаления:

  • сохранение объекта для возможного восстановления (например решает задачу случайного или преднамеренного удаления);
  • история (аудит);
  • меньшая нагрузка, в том числе при каскадном удалении данных;
  • отложенные операции обслуживания.

Пример на Python 3

from sqlalchemy import Column, Boolean, Integer, ForeignKey, String, DateTime
from sqlalchemy.orm import relationship

from . import Base
from .. import app


class User(Base):
    """
    Пользователь
    """
    __tablename__ = "user"

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False, unique=True)
    password = Column(String)
    created = Column(DateTime) # Дата создания
    last_activity = Column(DateTime) # Дата последней активности
    disable = Column(Boolean, default=False)

    pages = relationship("Page", primaryjoin="User.id==Page.user_id")

    def __init__(self, name):
        self.name = name
        self.created = datetime.datetime.now()
        self.last_activity = datetime.datetime.now()

    def __repr__(self):
        return '<User %r>' % (self.name)

    def as_dict(self, short=True):
        """
        Отобразить как словарь
        """
        if short:
            return {
                c.name: getattr(self, c.name)
                for c in self.__table__.columns
                if c.name not in ['password', 'created', 'last_activity', 'disable']
            }
        else:
            return {
                c.name: getattr(self, c.name)
                for c in self.__table__.columns
                if c.name!='password'
            }


class Page(Base):
    """
    Страница
    """
    __tablename__ = "page"

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('user.id'))
    parent_id = Column(Integer, ForeignKey('page.id'))
    title = Column(String)
    body = Column(String, default='')
    level = Column(Integer, default=0) # Уровень относительно родителя
    showing = Column(Boolean, default=False) # Показ всех дочерних
    created = Column(DateTime) # Дата создания
    updated = Column(DateTime) # Дата обновления

    trash = relationship(
        "TrashPage",
        primaryjoin="Page.id==TrashPage.page_id",
        uselist=False
    )

    def __init__(self, user, title):
        assert type(user).__name__=='User', app.logger.info('Не передан объект User')
        self.user_id = user.id
        self.title = title
        self.created = datetime.datetime.now()
        self.updated = datetime.datetime.now()

    def __repr__(self):
        return "<Page('{}')>".format(self.title)

    def as_dict(self):
        return {c.name: getattr(self, c.name) for c in self.__table__.columns}


class TrashPage(Base):
    """
    Корзина для страниц
    """
    __tablename__ = "trashpage"

    id = Column(Integer, primary_key=True)
    # ID пользователя
    user_id = Column(Integer, ForeignKey('user.id'))
    # ID страницы
    page_id = Column(Integer, ForeignKey('page.id'))
    # Дата удаления
    created = Column(DateTime)

    # Связи
    user = relationship(
        "User",
        primaryjoin="TrashPage.user_id==User.id",
        uselist=False
    )
    book = relationship(
        "Page",
        primaryjoin="TrashPage.page_id==Page.id",
        uselist=False
    )

    def __init__(self, user, page):
        assert type(user).__name__=='User', app.logger.info('Не передан объект User')
        assert type(page).__name__=='Page', app.logger.info('Не передан объект Page')
        self.user_id = user.id
        self.page_id = page.id
        self.created = datetime.datetime.now()


RemiZOffAlex   Создано: 2019-04-02   Обновлено: 2019-04-02