Коли краще зберігати прапори як бітову маску, а не використовувати асоціативну таблицю?


76

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

У яких випадках варіант 2 був би кращим?

Варіант 1

Використовуйте асоціативну таблицю.

Користувач
----
UserId (PK)
Ім'я
Кафедра
Дозвіл
----
PermissionId (PK)
Ім'я
User_Permission
----
Ідентифікатор користувача (FK)
PermissionId (FK)

Варіант 2

Зберігайте бітову маску для кожного користувача.

Користувач
----
UserId (PK)
Ім'я
Кафедра
Дозволи
[Flags]
enum Permissions {
    Read = 1,
    Create = 2,
    Download = 4,
    Print = 8,
    Approve = 16
}

Відповіді:


63

Чудове питання!

По-перше, давайте зробимо кілька припущень про "краще".

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

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

select * from user where permsission & CREATE = TRUE

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

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

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


5
Оскільки потужність логічного стовпця (або біта у випадку SQL Server) надзвичайно низька, індекс цих стовпців абсолютно марний. Отже, нормалізоване рішення також не має такої оптимізації.
Clodoaldo Neto

Чи не пакує SQL Server сусідні бітові поля в байти, в основному зберігаючи їх як бітову маску.
розчавити

12

Особисто я б використовував асоціативну таблицю.

Поле бітової маски дуже важко запитати та приєднатися до нього.

Ви завжди можете зіставити це з вашим переліченням прапорів C #, і якщо продуктивність стане, і випустіть рефакторинг бази даних.

Читаність при передчасній оптимізації;)


6
Управління та обслуговування. Наскільки важче буде підтримувати та керувати даними, що зберігаються в базі даних, коли критична інформація затуляється у стовпці бітової маски? І будь-який приріст продуктивності майже напевно не буде настільки великим, щоб мати реальні зміни.
Філіп Келлі,

5

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


5

Однозначної відповіді немає , тож робіть те, що вам підходить . Але ось мій улов:

Використовуйте варіант 1, якщо

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

Використовуйте варіант 2, якщо

  • Дозволи будуть обмежені кількома
  • Ви очікуєте мільйонів користувачів

Мільйони рядків - це тривіальне число в сучасних (і навіть пристойних спадщинах) СУБД
Адам Робінсон

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

1

Єдиний раз, коли я можу придумати, коли б я використовував поле бітової маски для зберігання дозволів, це коли ви дійсно обмежені тим, скільки фізичної пам’яті у вас .... як, можливо, на старому мобільному пристрої. По правді кажучи, обсяг збереженої пам'яті не вартий того. Навіть у мільйонів користувачів простір на жорсткому диску коштує дешево, і ви можете набагато простіше розширити дозволи тощо, використовуючи підхід без бітової маски (це про звітування про те, хто має які дозволи тощо).

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


1

Це буде корисно, коли вони не зміняться у своїй структурі і будуть використовуватися завжди разом. Таким чином, у вас є невеликі зворотні поїздки на сервер. Вони також хороші з точки зору продуктивності, оскільки ви можете вплинути на всі права в одному призначенні змінної.

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


1

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

Якщо ви зберігаєте його в рядку і намагаєтеся виконати роботу над ним на рівні БД, вам доведеться виконати гімнастику, щоб отримати дозволи для сторінки X, що може бути болісно.


1

Я не раджу використовувати бітову маску з таких причин:

  • Індекс не можна використовувати ефективно
  • Запити це складніше
  • На читабельність / технічне обслуговування дуже впливає
  • Пересічний розробник там не знає, що таке бітова маска
  • Гнучкість знижена (верхня межа до числа бітів у ряді)

Залежно від ваших шаблонів запитів, запланованого набору функцій та розподілу даних, я б вибрав ваш варіант 1 або навіть щось просте, як:

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

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

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

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)
)

-2

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


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

@Адам Робінсон, (1) Ні, це насправді зовсім не означає цього. Це означає, що запит буде працювати швидше , що правильно. (2) Ви порівнюєте найбільш оптимізований запит в асоціативній таблиці з найменш оптимізованим запитом у цілочисельному полі. Це насправді не дуже практично.
smartcaveman

1
Хоча цілком можливо, що код, який ви пишете для інтерпретації бітової маски, був би більш ефективним, ніж приєднання до USER_PERMISSIONтаблиці, здається малоймовірним, що різниця в продуктивності була б значущою - це навряд чи буде операцією вузького місця - і є суттєва втрата чіткості коду.
Джастін Кейв,

У вашій оригінальній версії було сказано "швидко", а не "швидше", як це робиться зараз, звідси мій перший коментар. Так, я порівнюю "найбільш оптимізований запит" для асоціативної версії, але це також версія, яка, швидше за все, буде на місці. Я порівнюю це із "найбільш погано оптимізованим" запитом у полі бітової маски, тому що, знову ж таки, це, мабуть, буде на місці. Неможливо створити польовий індекс на полі, і якщо ви плануєте перевірити дозволи як частину запиту, побітової операції не уникнути. У вас є кращий варіант для цього?
Адам Робінсон,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.