Проблема

Зачем использовать вообще одно из этих решений?
Существуют модели, у которых необходимо выделить разновидности и сделать это именно с помощью типов, а не категорий... Разберёмся...

Тип - разновидность модели, не всегда, но влияющая на состав её параметров

Категория - одна из характеристик модели, указывающая на принадлежность к разновидности, не влияющая на состав её параметров

Решение

Решить данную проблему можно 2 основными способами:

  1. Использовать enums (перечисления)

  2. Использовать таблицы

Использование enums более пригодно при наличии небольшого кол-ва типов (до 5 на каждый enum)

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


Практическая ситуация

Есть база данных, в которой необходимо выделить разновидности "счетов" для приложения по учёту домашней бухгалтерии.

Счета бывают :

  • Дебетовые

  • Кредитные:

    • Ипотека

    • Автокредит

    • Потребительский кредит

    • Рассрочка

    • Долг

  • Депозитные:

    • Объекты (материальные):

      • Недвижимость

      • Транспорт

      • Ценная вещь

    • Вклады (не материальные):

      • Депозит (банковский вклад)

      • Депозит (копилка)

      • Инвестиции

      • Долг

Я разработал два варианта диаграмм и структур данных для решения конкретной ситуации:

\Диаграмма базы данных с использованием таблицы типов
\Диаграмма базы данных с использованием таблицы типов
Диаграмма базы данных с использований enums (перечислений)
Диаграмма базы данных с использований enums (перечислений)

Тестирование: Enums VS Tables

Данные для тестирования:
18000 записей = 10000 счетов + 1500 кредитных + 3500 объектов + 3000 вкладов

Использовал СУБД PostgreSQL, так как она мне нравится больше остальных.

Тестирование проводил 10 раз, чтобы получить более усреднённые данные.

Для соединения .NET приложения с базой данных использовались: ORM (Entity Framework) и ADO.NET (Npgsql).

На картинках измерение в миллисекундах!

Генерация данных

Первое - (неважное) генерация данных с помощью .NET Faker
Тут оказалось, что быстрее генерируется схема с таблицами, хотя с точки зрения моделей действия для генерация не отличаются.

Генерация моделей (мс)
Генерация моделей (мс)

Добавление данных

Второе испытание - добавление всех 18000 записей в базу данных.

Для Entity Framework просто добавление записей.

А вот для ADO.NET (Npgsql) добавление сделаем несколькими способами:

  • Все записи добавляются 1 запросом

  • Пакеты по <= 1000 запросов

  • Пакеты по <= 800 запросов

  • Пакеты по <= 500 запросов

Очевидно, что самое долгое - это добавление всех записей 1 запросом... в среднем около 19-20сек.

Интересное:
Сравниваем добавление через Entity Framework и пакетную отправку (исследование внутри исследования)

Оказалось, что скорости Entity Framework по добавлению +- сопоставляются с отправкой пакетами по <= 1000 запросов.
Поэтому пакеты меньшего размера будут давать прирост производительности.

Максимального прироста смог добиться, отправляя пакеты по <= 500 запросов, тем самым обогнав Entity Framework на 0,5-0,7сек или же на 33-38%!

... Возвращаемся к исследованию Enums VS Tables:

Единственное измерение, где подход таблиц смог "победить" enums - это добавление всех записей 1 запросом... и то в районе погрешности - всего на 1,6%... не густо...

Во всех остальных замерах на лицо превосходство enums подхода.
В среднем он быстрее на 13%:

  • Добавление через Entity Framework - 19%

  • Добавление 1 запросом - -1.6%

  • Добавление пакетами по <= 1000 запросов - 11%

  • Добавление пакетами по <= 800 запросов - 9%

  • Добавление пакетами по <= 500 запросов - 14%

Добавление записей в базу данных (мс)
Добавление записей в базу данных (мс)

Разница между tables и enums при добавлении данных (% от tables подхода)

Добавление через Entity Framework

19%

Добавление 1 запросом

- 1,6% (в зоне погрешности)

Добавление пакетами по <= 1000 запросов

11%

Добавление пакетами по <= 800 запросов

9%

Добавление пакетами по <= 500 запросов

14%

Получение одной записи

Алгоритм получения записи следующий:

  • Получаем запись счета по Id

  • Определяем - какой тип счета у записи

  • Лезем в соответствующую таблицу и достаём оттуда запись по Id

Тут ситуация неоднозначная:
Если использовать Entity Framework, то enums быстрее табличного подхода на 3%.
Если же использовать Npgsql, то табличный подход быстрее enums на 5%.

Эти значения в рамках погрешности, так что буду считать, что разницы при получении 1 записи между подходами нету!

Получение одной записи из базы данных (мс)
Получение одной записи из базы данных (мс)

Вывод?

В добавлении записей выигрывает enums подход (подход перечислений) в среднем на 13%.

Но у enums есть ограничения, которые не позволяют их использовать во всех ситуациях, когда нужно ввести типы моделей:

  • Они неизменяемы (да, добавить новое значение можно... но удалить ненужное уже трудно)

  • Лучше хранить не более 5-7 значений в одном enum, чтобы они не превратились в номенклатуру

  • Требует уверенных знаний SQL

Выбирать enum подход (подход перечислений) стоит, если:

  • Не нужна структура подтипов

  • Не планируется изменение списка значений

  • Количество типов не превышает 5-7 на одно перечисление

Репозиторий с проектом тестирования

Комментарии (4)


  1. onets
    02.10.2024 11:52
    +2

    Тут не все так просто. Enum - встроен в язык программирования и обычно на нем завязана бизнес логика в коде. Для C# это означает, что нельзя просто взять и добавить значение enum (aka добавить в таблицу). Потому что нет компилируемого кода, который будет обрабатывать новое значение enum-а.

    Следовательно в данном случае - нет никакого смысла делать enum-ы как таблицы.

    Но, когда вы начинаете оптимизации и переходите на нативный sql (например экран списка), то там появляются проблемы.

    Чтобы отобразить текстовое описание enum-а - надо его подставить откуда-то. Если enum у вас на стороне C# - значит подмена будет на сервере или на клиенте. Но что это означает для пользователя? Сортировка по колонке сломается и будет сортировать по int значению, которое лежит в базе данных, а не по алфавиту для текстового описания.

    Решить это можно переносом enum в таблицу БД и дополнительным джоином при выборке данных для экрана списка.

    А там еще локализация на подходе...

    И не очень понятно причем тут генерация и производительность ORM и нативного SQL.


    1. anavov4ik Автор
      02.10.2024 11:52
      +1

      Спасибо за обратную связь, очень приятно её увидеть от более компетентного человека!
      Изначально передо мной встал выбор - что использовать: enum-ы (которые создаются в СУБД) или отдельную таблицу.
      Основным аспектом для сравнения решил взять скорость выполнения запросов.
      Пока изучал и делал замеры, решил их красиво оформить в таблички и написать статью о получившихся результатах.

      Можете подсказать, какие моменты стоит учесть и рассмотреть, чтобы сделать данную статью максимально соответствующей теме и полно раскрывающей её? (Мне удобнее будет, если ответите в лс)


  1. amironov
    02.10.2024 11:52

    Они неизменяемы (да, добавить новое значение можно... но удалить ненужное уже трудно)

    С добавлением тоже есть проблема: не все версии постгреса поддерживают изменение enum в транзакции. В том же ef core это ломает миграции.


  1. LaRN
    02.10.2024 11:52

    Если в интерфейсе нужно показать лукап с типами, например для фильтрации счетов, то как быть? В случае с таблицей каждому типу можно хранить человекочитаемую расшифровку, что удобно для показа в ui.