Pregunta

En mi tiempo libre comencé a escribir un pequeño juego multijugador con una base de datos. Estaba buscando separar la información de inicio de sesión del jugador de otra información del juego (inventario, estadísticas y estado) y un amigo que me mencionó podría no ser la mejor idea.

¿Sería mejor agrupar todo en una tabla?

¿Fue útil?

Solución

Comience por ignorar el rendimiento y cree el diseño de base de datos más lógico y fácil de entender que pueda. Si los jugadores y los objetos del juego (¿espadas? ¿Piezas de ajedrez?) Son cosas conceptualmente separadas, entonces colóquelas en mesas separadas. Si un jugador puede llevar cosas, pones una clave externa en " things " tabla que hace referencia a los " jugadores " mesa. Y así sucesivamente.

Luego, cuando tengas cientos de jugadores y miles de cosas, y los jugadores corran en el juego y hagan cosas que requieran búsquedas en la base de datos, bueno, tu diseño aún será lo suficientemente rápido, si acaba de agregar los índices apropiados.

Por supuesto, si planifica para miles de jugadores simultáneos, cada uno de ellos haciendo un inventario de sus cosas cada segundo, o tal vez alguna otra carga enorme de búsquedas en la base de datos (¿una búsqueda de cada cuadro renderizado por el motor de gráficos?) entonces necesitará pensar en el rendimiento. Pero en ese caso, una base de datos relacional basada en disco típica no será lo suficientemente rápida, sin importar cómo diseñe sus tablas.

Otros consejos

Las bases de datos relacionales funcionan en conjuntos, hay una consulta de base de datos para entregar ninguno (" no encontrado ") o más resultados. No se pretende que una base de datos relacional entregue siempre exactamente un resultado mediante la ejecución de una consulta (en las relaciones).

Dijo que quiero mencionar que también existe la vida real ;-). Una relación uno a uno podría tener sentido, es decir, si el recuento de columnas de la tabla alcanza un nivel crítico, podría ser más rápido dividir esta tabla. Pero este tipo de condiciones realmente son raras.

Si realmente no necesita dividir la tabla, simplemente no lo haga. Al menos, asumo que en su caso tendría que seleccionar dos tablas para obtener todos los datos. Esto probablemente sea más lento que almacenar todo en una tabla. Y su lógica de programación al menos será un poco menos legible al hacerlo.

Edith dice : Comentando sobre la normalización: Bueno, nunca vi una base de datos normalizada a su máxima expresión y nadie lo recomienda como una opción. De todos modos, si no sabe exactamente si alguna columna se normalizará (es decir, se colocará en otras tablas) más adelante, también es más fácil hacerlo con una tabla en lugar de " tabla " -uno a uno- " otra tabla "

Si conservar

  

información de inicio de sesión del jugador [separada] de otra en   información del juego (inventario, estadísticas,   y estado)

es a menudo una cuestión de normalización. Es posible que desee echar un vistazo rápido a la wikipedia ( Tercera forma normal , Denormalization ) definiciones. Si los separa en varias tablas depende de qué datos se almacenan. Expandir tu pregunta hará esto concreto. Los datos que queremos almacenar son:

  • nombre de usuario: nombre único que permite a un usuario iniciar sesión en el sistema
  • passwd: password: asociado con el nombre de usuario que garantiza que el usuario es único
  • correo electrónico: dirección de correo electrónico para el usuario; por lo que el juego puede " empujar " el usuario cuando no ha jugado recientemente
  • carácter: posiblemente el nombre del juego para el carácter
  • estado: es el jugador y / o el personaje actualmente activo
  • hitpoints: una estadística de ejemplo para el personaje

Podríamos almacenar esto como una sola tabla o como múltiples tablas.

Ejemplo de tabla única

Si los jugadores tienen un solo personaje en este mundo de juego, entonces una sola mesa puede ser apropiada. Por ejemplo,

CREATE TABLE players(
  id         INTEGER NOT NULL,
  username   VARCHAR2(32) NOT NULL,
  password   VARCHAR2(32) NOT NULL,
  email      VARCHAR2(160),
  character  VARCHAR2(32) NOT NULL,
  status     VARCHAR2(32),
  hitpoints  INTEGER,

  CONSTRAINT pk_players PRIMARY KEY (uid)
);

Esto le permitiría encontrar toda la información pertinente sobre un jugador con una sola consulta en la tabla de jugadores.

Ejemplo de tabla múltiple

Sin embargo, si quisiera permitir que un solo jugador tenga múltiples personajes diferentes, querría dividir estos datos en dos tablas diferentes. Por ejemplo, podría hacer lo siguiente:

-- track information on the player
CREATE TABLE players(
  id         INTEGER NOT NULL,
  username   VARCHAR2(32) NOT NULL,
  password   VARCHAR2(32) NOT NULL,
  email      VARCHAR2(160),

  CONSTRAINT pk_players PRIMARY KEY (id)
);

-- track information on the character
CREATE TABLE characters(
  id         INTEGER NOT NULL,
  player_id  INTEGER NOT NULL,
  character  VARCHAR2(32) NOT NULL,
  status     VARCHAR2(32),
  hitpoints  INTEGER,

  CONSTRAINT pk_characters PRIMARY KEY (id)
);
-- foreign key reference
ALTER TABLE characters
  ADD CONSTRAINT characters__players
  FOREIGN KEY (player_id) REFERENCES players (id);

Este formato requiere uniones cuando se mira información tanto para el jugador como para el personaje; sin embargo, hace que la actualización de los registros sea menos probable que resulte en una inconsistencia. Este último argumento se usa normalmente cuando se abogan por tablas múltiples. Por ejemplo, si tuviera un jugador (LotRFan) con dos caracteres (gollum, frodo) en el formato de tabla única, tendría los campos de nombre de usuario, contraseña y correo electrónico duplicados. Si el jugador desea cambiar su correo electrónico / contraseña, deberá modificar ambos registros. Si solo se modificó un registro, la tabla se volverá inconsistente. Sin embargo, si LotRFan quería cambiar su contraseña en el formato de tabla múltiple, se actualiza una sola fila de la tabla.

Otras consideraciones

La utilización de una sola tabla o varias tablas depende de los datos subyacentes. Lo que no se ha descrito aquí, pero se observó en otras respuestas es que las optimizaciones a menudo toman dos tablas y las combinan en una sola tabla.

También es de interés conocer los tipos de cambios que se realizarán. Por ejemplo, si eligiera usar una sola tabla para los jugadores que pueden tener varios personajes y desea realizar un seguimiento del número total de comandos emitidos por el jugador, incrementando un campo en la tabla de jugadores; esto daría como resultado que se actualicen más filas en el ejemplo de una sola tabla.

El inventario es una relación de muchos a uno de todos modos, por lo que probablemente merece su propia tabla (indexada en su clave externa al menos).

También es posible que desee tener cosas personales para cada usuario separadas de las cosas públicas (es decir, lo que otros pueden ver de usted). Eso puede proporcionar mejores resultados de caché, ya que muchas personas verán sus atributos públicos, pero solo usted verá sus atributos personales.

Una relación 1-1 es solo una división vertical. Nada más. hágalo si, por alguna razón, no almacenará todos los datos en la misma tabla (por ejemplo, para particionar en varios servidores, etc.)

Como puede ver, el consenso general sobre esto es que " depende " ;. La optimización de rendimiento / consulta viene a la mente fácilmente, como lo mencionaron @Campbell y otros.

Pero creo que para el tipo de base de datos específica de la aplicación que está creando, es mejor comenzar considerando el diseño general de la aplicación. Para las bases de datos específicas de la aplicación (a diferencia de las bases de datos diseñadas para ser utilizadas con muchas aplicaciones o herramientas de informes), el modelo de datos "ideal" es probablemente el que mejor se asigna a su modelo de dominio como se implementa en su código de aplicación.

No puede llamar a su diseño MVC , y no lo hago sepa qué idioma está utilizando, pero espero que tenga algún código o clases que envuelvan el acceso a los datos con un modelo lógico. Creo que el mejor indicador de cómo su base de datos debería idealmente ser estructurada es cómo ve el diseño de la aplicación en este nivel.

En general, esto significa que el mejor lugar para comenzar es una asignación uno a uno de objetos de dominio a tablas de bases de datos.

Creo que es mejor ilustrar lo que quiero decir con el ejemplo:

Caso 1: una clase / una tabla

Piensas en términos de una clase de Jugador . Player.authenticate, Player.logged_in ?, Player.status, Player.hi_score, Player.gold_credits. Siempre que todas estas sean acciones en un solo usuario, o representen atributos de un solo valor de un usuario, entonces una sola tabla debe ser su punto de partida desde la perspectiva del diseño lógico de la aplicación. Clase única (Jugador), mesa única (jugadores).

Caso 2: Clases múltiples / Una o más tablas

Tal vez no me guste el diseño de la clase Player de swiss-army-knife, pero prefiero pensar en términos de User , Inventory , Cuadro de indicadores y Cuenta . User.authenticate, User.logged_in ?, User- > account.status, Scoreboard.hi_score (User), User- > account.gold_credits.

Probablemente esté haciendo esto porque creo que separar estos conceptos en el modelo de dominio hará que mi código sea más claro y más fácil de escribir. En este caso, el mejor punto de partida es una asignación directa de las clases de dominio a tablas individuales: usuarios, inventario, puntuaciones, cuentas, etc.

Hacer práctico el 'ideal'

Puse algunas palabras arriba para indicar que el mapeo uno a uno de los objetos del dominio a las tablas es el diseño ideal, lógico .

Ese es mi punto de partida preferido. Tratar de ser demasiado inteligente desde el punto de vista de los murciélagos, como asignar varias clases a una sola tabla, tiende a invitar a los problemas que prefiero evitar (puntos muertos y problemas de concurrencia en particular).

Pero no siempre es el final de la historia. Hay consideraciones prácticas que pueden significar que se necesita una actividad separada para optimizar el modelo de datos físicos, por ejemplo. problemas de concurrencia / bloqueo? el tamaño de la fila es demasiado grande? indexación de gastos generales? rendimiento de consulta, etc.

Sin embargo, tenga cuidado al pensar en el rendimiento: solo porque puede ser una fila en una tabla, probablemente no signifique que se solicite solo una vez para obtener la fila completa. Me imagino que el escenario de juego multiusuario puede involucrar una serie completa de llamadas para obtener información de diferentes jugadores en diferentes momentos, y el jugador siempre desea el "valor actual", que puede ser bastante dinámico. Eso puede traducirse en muchas llamadas para solo unas pocas columnas en la tabla cada vez (en cuyo caso, un diseño de varias tablas podría ser más rápido que un diseño de una sola tabla).

Recomiendo separarlos. No por razones de base de datos, sino por razones de desarrollo.

Cuando estés codificando el módulo de inicio de sesión de tu jugador, no querrás pensar en la información del juego. Y visa a la inversa. Será más fácil mantener una separación de preocupaciones si las tablas son distintas.

Se reduce al hecho de que el módulo de información de tu juego no tiene problemas con la tabla de inicio de sesión del jugador.

Probablemente también lo ponga en una tabla en esta situación ya que el rendimiento de su consulta será mejor.

En realidad, solo desea utilizar la siguiente tabla cuando haya un elemento de datos duplicados por el que debería utilizar la normalización para reducir sus gastos generales de almacenamiento de datos.

Estoy de acuerdo con la La respuesta de Thomas Padron-McCarthy :

Resolver para el caso de miles de usuarios simultáneos no es su problema. Hacer que la gente juegue tu juego es el problema. Comience con el diseño más simple: finalice el juego, trabaje, juegue y sea fácilmente expandible. Si el juego despega, entonces se normaliza.

También vale la pena mencionar que los índices se pueden ver como tablas separadas que contienen una vista más estrecha de los datos.

La gente ha podido inferir un diseño utilizado por Blizzard para World of Warcraft. Parece que las misiones en las que está un jugador se almacenan con el personaje. por ejemplo:

CREATE TABLE Players (
   PlayerID int,
   PlayerName varchar(50),
   ...
   Quest1ID int,
   Quest2ID int,
   Quest3ID int,
   ...
   Quest10ID int,
   ...
)

Inicialmente podría estar en la mayoría de las 10 misiones, luego se amplió a 25, por ejemplo:

CREATE TABLE Players (
   PlayerID int,
   PlayerName varchar(50),
   ...
   Quest1ID int,
   Quest2ID int,
   Quest3ID int,
   ...
   Quest25ID int,
   ...
)

Esto contrasta con un diseño dividido:

CREATE TABLE Players (
   PlayerID int,
   PlayerName varchar(50),
   ...
)

CREATE TABLE PlayerQuests (
   PlayerID int,
   QuestID int
)

Este último es mucho más fácil de tratar conceptualmente, y permite el número arbitrario de misiones. Por otro lado, debes unirte a Players y PlayerQuests para obtener toda esa información.

Le sugiero que mantenga cada tipo de registro en su propia tabla.

Los inicios de sesión de jugador son un tipo de registro. El inventario es otro. No deben compartir una tabla, ya que la mayoría de la información no se comparte. Mantenga sus tablas simples separando la información no relacionada.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top