Когда лучше хранить флаги в виде битовой маски, а не использовать ассоциативную таблицу?

Im работает над приложением, в котором пользователи имеют разные разрешения для использования разных функций (например, Чтение, создание, загрузка, печать, утверждение и т.д.). Список разрешений не должен часто меняться. У меня есть несколько вариантов сохранения этих разрешений в базе данных.

В каких случаях вариант 2 был бы лучше?

Вариант 1

Используйте ассоциативную таблицу.

User
----
UserId (PK)
Name
Department
Permission
----
PermissionId (PK)
Name
User_Permission
----
UserId (FK)
PermissionId (FK)

Вариант 2

Сохраните битмаску для каждого пользователя.

User
----
UserId (PK)
Name
Department
Permissions
[Flags]
enum Permissions {
    Read = 1,
    Create = 2,
    Download = 4,
    Print = 8,
    Approve = 16
}

Ответ 1

Великолепный вопрос!

Во-первых, допустим некоторые предположения о "лучшем".

Я предполагаю, что вы не очень заботитесь о дискового пространства - битовая маска эффективна с пространственной точки зрения, но я не уверен, что это важно, если вы используете SQL-сервер.

Я предполагаю, что вам действительно нужна скорость. Битовая маска может быть очень быстрой при использовании вычислений - но вы не сможете использовать индекс при запросе битовой маски. Это не должно иметь большого значения, но если вы хотите узнать, какие пользователи создали доступ, ваш запрос будет похож на

select * from user where permsission & CREATE = TRUE

(сегодня у нас нет доступа к SQL Server, в дороге). Этот запрос не сможет использовать индекс из-за математической операции, поэтому, если у вас огромное количество пользователей, это будет довольно болезненно.

Я предполагаю, что вы заботитесь о ремонтопригодности. С точки зрения ремонтопригодности битовая маска не так выразительна, как основной проблемный домен, как сохранение явных разрешений. Вам почти наверняка придется синхронизировать значение флажков битмаски для нескольких компонентов, включая базу данных. Не невозможно, но боль в задней части.

Итак, если нет другого способа оценки "лучше", я бы сказал, что маршрут битмаски не так хорош, как сохранение разрешений в нормализованной структуре базы данных. Я не согласен с тем, что это будет "медленнее, потому что вам нужно сделать соединение" - если у вас нет полностью дисфункциональной базы данных, вы не сможете ее измерить (тогда как запрос без использования активного индекса может стать заметным медленнее даже с несколькими тысячами записей).

Ответ 2

Лично я бы использовал ассоциативную таблицу.

Поле битмаски очень сложно запросить и присоединиться.

Вы всегда можете сопоставить это с вашим перечислением флагов С# и если производительность становится и реорганизация базы данных.

Считываемость перед преждевременной оптимизацией;)

Ответ 3

Сохраните разрешения, нормализованные (т.е. не в битовой маске). Хотя это явно не является требованием для вашего сценария (особенно если разрешения не будут часто меняться), это сделает запрос намного проще и понятнее.

Ответ 4

Существует окончательный ответ, поэтому делать то, что работает для вас. Но вот мой улов:

Используйте параметр 1, если

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

Используйте опцию 2, если

  • Разрешения ограничиваются небольшими
  • Вы ожидаете миллионы пользователей.

Ответ 5

Единственный момент, когда я могу использовать поле битмаски для хранения разрешений, - это когда вы действительно ограничены в том, сколько физической памяти у вас есть... например, возможно, на старом мобильном устройстве. По правде говоря, объем памяти, который вы сохраняете, не стоит. Даже на миллионах пользователей на жестком диске дешево, и вы можете расширять права доступа и т.д. Намного проще, используя небитальный подход (это сообщение о том, у кого есть какие разрешения и т.д.).

Одна из этих самых больших головных болей, с которыми я столкнулся, - это назначение прав пользователей непосредственно в базе данных. Я знаю, что вы должны попробовать и использовать приложение для администрирования себя и не очень с данными приложения в целом, но иногда это просто необходимо. Если битмаска на самом деле не является символьным полем, и вы можете легко увидеть, какие разрешения у кого-то есть вместо целого числа, попробуйте объяснить аналитику и т.д., Как дать доступ на запись и т.д. Кому-то, обновив поле..... и молитесь ваша арифметика верна.

Ответ 6

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

Мне лично они не нравятся... В некоторых приложениях с интенсивным использованием они все еще используются. Я помню, как реализовал шахматный AI, используя их, потому что вы могли оценить плату в одном сравнении. Это боль, с которой можно работать.

Ответ 7

Я бы всегда сохранял его нормализованным, если только база данных просто не удерживает запись для вас, и вы никогда ничего не сделаете с этим, кроме извлечения и сохранения. Сценарий для этого заключается в том, что при входе в систему выбирается строка пользовательских прав, а в коде сервера обрабатывается и кэшируется. В этом случае это действительно не имеет большого значения, что он денормализован.

Если вы храните его в строке и пытаетесь работать над ним на уровне БД, вам нужно будет сделать некоторую гимнастику, чтобы получить разрешения на страницу X, что может быть болезненным.

Ответ 8

Я советую использовать битмаску по следующим причинам:

  • Индекс не может эффективно использоваться
  • Запрос сложнее
  • Считывание/обслуживание сильно пострадали.
  • Средний разработчик там не знает, что такое битовая маска
  • Гибкость снижается (верхний предел до nr бит в числе)

В зависимости от ваших шаблонов запросов, запланированного набора функций и распределения данных я бы пошел с вашим вариантом 1 или даже с чем-то простым:

user_permissions(
   user_id
  ,read     
  ,create   
  ,download 
  ,print    
  ,approve  
  ,primary key(user_id)
);

Добавление столбца - это модификация схемы, но я предполагаю, что добавление привилегии "Очистка" потребует от кода некоторого кода, поэтому привилегии могут быть не такими динамичными, как вы думаете.

Если у вас есть больное распределение данных, например, 90% базы пользователей не имеют единого разрешения, следующая модель также отлично работает (но разваливается при больших сканированиях (одно пятистороннее соединение против однократное сканирование полной таблицы).

user_permission_read(
   user_id
  ,primary key(user_id)
  ,foreign key(user_id) references user(user_id)
)

user_permission_write(
   user_id
  ,primary key(user_id)
  ,foreign key(user_id) references user(user_id)
)

user_permission_etcetera(
   user_id
  ,primary key(user_id)
  ,foreign key(user_id) references user(user_id)
)

Ответ 9

Ваши запросы будут работать быстрее с использованием перечисления флагов (битмаски), потому что вам не нужно включать соединение в связанную таблицу, чтобы понять значение.