¿Cuándo es mejor almacenar banderas como una máscara de bits en lugar de usar una tabla asociativa?


76

Estoy trabajando en una aplicación en la que los usuarios tienen diferentes permisos para usar diferentes funciones (por ejemplo, leer, crear, descargar, imprimir, aprobar, etc.). No se espera que la lista de permisos cambie con frecuencia. Tengo un par de opciones sobre cómo almacenar estos permisos en la base de datos.

¿En qué casos sería mejor la opción 2?

Opción 1

Utilice una tabla asociativa.

Usuario
----
UserId (PK)
Nombre
Departamento
Permiso
----
PermissionId (PK)
Nombre
User_Permission
----
UserId (FK)
PermissionId (FK)

opcion 2

Almacene una máscara de bits para cada usuario.

Usuario
----
UserId (PK)
Nombre
Departamento
Permisos
[Flags]
enum Permissions {
    Read = 1,
    Create = 2,
    Download = 4,
    Print = 8,
    Approve = 16
}

Respuestas:


63

¡Espléndida pregunta!

En primer lugar, hagamos algunas suposiciones sobre "mejor".

Supongo que no le importa mucho el espacio en disco: una máscara de bits es eficiente desde el punto de vista del espacio, pero no estoy seguro de que eso importe mucho si está utilizando un servidor SQL.

Supongo que te importa la velocidad. Una máscara de bits puede ser muy rápida cuando se utilizan cálculos, pero no podrá utilizar un índice al consultar la máscara de bits. Esto no debería importar mucho, pero si desea saber qué usuarios tienen acceso de creación, su consulta sería algo como

select * from user where permsission & CREATE = TRUE

(no tengo acceso a SQL Server hoy, en el camino). Esa consulta no podría usar un índice debido a la operación matemática, por lo que si tiene una gran cantidad de usuarios, esto sería bastante doloroso.

Supongo que le importa la mantenibilidad. Desde el punto de vista de la capacidad de mantenimiento, la máscara de bits no es tan expresiva como el dominio del problema subyacente como el almacenamiento de permisos explícitos. Es casi seguro que tendrá que sincronizar el valor de los indicadores de la máscara de bits en varios componentes, incluida la base de datos. No imposible, pero dolor en el trasero.

Entonces, a menos que haya otra forma de evaluar "mejor", diría que la ruta de la máscara de bits no es tan buena como almacenar los permisos en una estructura de base de datos normalizada. No estoy de acuerdo en que sea "más lento porque tiene que unirse"; a menos que tenga una base de datos totalmente disfuncional, no podrá medir esto (mientras que las consultas sin el beneficio de un índice activo pueden volverse notablemente más lento incluso con unos pocos miles de registros).


5
Como la cardinalidad de una columna booleana (o bit en el caso de SQL Server) es extremadamente baja, un índice en esas columnas es totalmente inútil. Entonces, la solución normalizada tampoco tendría esa optimización disponible.
Clodoaldo Neto

SQL Server no empaqueta los campos de bits adyacentes en bytes, básicamente los almacena como una máscara de bits.
aplastar el

12

Personalmente, usaría una tabla asociativa.

Un campo de máscara de bits es muy difícil de consultar y unirse.

Siempre puede asignar esto a su enumeración de banderas de C # y, si el rendimiento se convierte en un problema, refactorice la base de datos.

Legibilidad sobre optimización prematura;)


6
Gestión y mantenimiento. ¿Cuánto más difícil será mantener y administrar los datos almacenados en la base de datos cuando la información crítica se ofusque en una columna de máscara de bits? Y es casi seguro que cualquier mejora en el rendimiento no será lo suficientemente grande como para marcar una diferencia real.
Philip Kelley

5

Almacene los permisos normalizados (es decir, no en una máscara de bits). Si bien obviamente no es un requisito para su escenario (especialmente si los permisos no cambian con frecuencia), hará que las consultas sean mucho más fáciles y obvias.


5

No hay una respuesta definitiva , así que haga lo que le funcione . Pero aquí está mi truco:

Utilice la opción 1 si

  • Espera que los permisos aumenten a muchos
  • Si es posible que necesite realizar una verificación de permisos en los propios procedimientos almacenados de la base de datos
  • No esperas millones de usuarios para que los registros en la tabla no crezcan masivamente

Utilice la opción 2 si

  • Los permisos se limitarán a unos pocos
  • Esperas millones de usuarios

Millones de filas es un número trivial en los RDBMS modernos (e incluso de un legado decente)
Adam Robinson

Sí, pero teniendo en cuenta los índices que pueda necesitar y la posibilidad de marcar el índice durante la búsqueda, lo que ralentizará el proceso, prefiero la segunda opción.
Aliostad

1

La única vez que puedo pensar en cuándo usaría un campo de máscara de bits para almacenar permisos, es cuando realmente está realmente limitado en la cantidad de memoria física que tiene ... como tal vez en un dispositivo móvil antiguo. En verdad, la cantidad de memoria que ahorras no vale la pena. Incluso para millones de usuarios, el espacio en el disco duro es barato, y puede ampliar los permisos, etc., mucho más fácilmente utilizando el enfoque sin máscara de bits (se trata de informar sobre quién tiene qué permisos, etc.)

Uno de los mayores dolores de cabeza con los que me he encontrado es la asignación de permisos a los usuarios directamente en la base de datos. Sé que debería probar y usar la aplicación para administrarse a sí misma y no mucho con los datos de la aplicación en general, pero a veces, es solo necesario. A menos que la máscara de bits sea en realidad un campo de caracteres, y pueda ver fácilmente qué permisos tiene alguien en lugar de un número entero, intente explicarle a un analista, etc., cómo otorgar acceso de escritura, etc.a alguien actualizando el campo ... y rezar tu aritmética es correcta.


1

Será útil cuando no cambien en su estructura y siempre se usen juntos. De esa manera, tiene pequeños viajes de ida y vuelta al servidor. También son buenos en cuanto al rendimiento porque puede afectar todos los derechos en una sola asignación de una variable.

Personalmente, no me gustan ... En algunas aplicaciones de alto rendimiento, todavía se usan. Recuerdo haber implementado una IA de ajedrez usando estos porque se podía evaluar un tablero en una sola comparación. Es un dolor trabajar con él.


1

Siempre lo almacenaría normalizado a menos que la base de datos simplemente contenga el registro por usted, y nunca hará nada con esto además de recuperar y guardar. Un escenario para esto es si al iniciar sesión, se obtiene la cadena de permiso de su usuario y, en el código del servidor, se procesa y almacena en caché. En ese caso, realmente no importa demasiado que esté desnormalizado.

Si lo está almacenando en una cadena y está tratando de trabajar en él a nivel de base de datos, tendrá que hacer algo de gimnasia para obtener los permisos para la página X, lo que puede ser doloroso.


1

Aconsejo no usar una máscara de bits por las siguientes razones:

  • El índice no se puede usar de manera eficiente
  • Consultar es más difícil
  • La legibilidad / mantenimiento se ve gravemente afectada
  • El desarrollador promedio no sabe qué es una máscara de bits
  • Se reduce la flexibilidad (límite superior a nr de bits en un número)

Dependiendo de sus patrones de consulta, conjunto de características planificadas y distribución de datos, elegiría su opción 1, o incluso algo simple como:

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

Agregar una columna es una modificación del esquema, pero supongo que agregar un privilegio "Purgar" requerirá algún código que lo acompañe, por lo que es posible que los privilegios no tengan que ser tan dinámicos como cree.

Si tiene una distribución de datos enferma, como que el 90% de la base de usuarios no tiene un solo permiso, el siguiente modelo también funciona bien (pero se desmorona cuando se realizan escaneos más grandes (una combinación de 5 vías frente a una sola tabla completa escanear).

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

Sus consultas se ejecutarán más rápido usando una enumeración de banderas (máscara de bits), porque no necesitará incluir una combinación a la tabla asociada para entender el valor.


4
-1 Esto implica incorrectamente que será no correr rápido usando una combinación. Tampoco lo hace la cuenta de lo que la consulta es . Si está verificando la presencia de un permiso en particular, una combinación en una columna correctamente indexada volará las puertas de un campo de máscara de bits, cuyas operaciones bit a bit requerirían un escaneo de tabla.
Adam Robinson

@ Adam Robinson, (1) No, realmente no implica eso en absoluto. Implica que la consulta se ejecutará más rápido , lo cual es correcto. (2) Está comparando la consulta más optimizada en una tabla asociativa con la consulta menos optimizada en un campo entero. Eso realmente no es muy práctico.
smartcaveman

1
Si bien es posible que el código que escriba para interpretar la máscara de bits sea más eficiente que una unión a la USER_PERMISSION tabla, parece poco probable que la diferencia de rendimiento sea significativa (es poco probable que sea la operación de cuello de botella) y hay una pérdida sustancial de claridad en el código.
Justin Cave

Su versión original decía "rápido", no "más rápido", como lo hace ahora, de ahí mi primer comentario. Sí, estoy comparando "la consulta más optimizada" para la versión asociativa, pero también es la versión que es más probable que esté implementada. Estoy comparando eso con la consulta "más pobremente optimizada" en el campo de máscara de bits porque, nuevamente, eso es lo que probablemente estará en su lugar. No hay forma de crear un índice bit a bit en un campo, y si planea verificar los permisos como parte de la consulta, una operación bit a bit es inevitable. ¿Tiene una mejor opción para hacer eso?
Adam Robinson
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.