Domanda

Nel mio tempo libero ho iniziato a scrivere un piccolo gioco multiplayer con un back-end di database. Stavo cercando di separare le informazioni di accesso del giocatore dalle altre informazioni sul gioco (inventario, statistiche e stato) e un amico cresciuto potrebbe non essere la migliore idea.

Sarebbe meglio raggruppare tutto insieme in una tabella?

È stato utile?

Soluzione

Inizia ignorando le prestazioni e crea il design del database più logico e di facile comprensione che puoi. Se i giocatori e gli oggetti di gioco (spade? Pezzi degli scacchi?) Sono concettualmente separati, mettili in tabelle separate. Se un giocatore può trasportare cose, metti una chiave esterna in " cose " tabella che fa riferimento ai "giocatori" tavolo. E così via.

Quindi, quando hai centinaia di giocatori e migliaia di cose, e i giocatori corrono nel gioco e fanno cose che richiedono ricerche nel database, beh, il tuo design sarà ancora abbastanza veloce, se devi solo aggiungere gli indici appropriati.

Ovviamente, se pianifichi migliaia di giocatori simultanei, ognuno di loro fa l'inventario delle sue cose ogni secondo, o forse qualche altro enorme carico di ricerche nel database (una ricerca per ogni fotogramma reso dal motore grafico?) allora avrai bisogno a pensare alle prestazioni. Ma in quel caso, un tipico database relazionale basato su disco non sarà comunque abbastanza veloce, non importa come si progettano le tabelle.

Altri suggerimenti

I database relazionali funzionano su set, una query di database è lì per fornire nessuno ("non trovato") o più risultati. Un database relazionale non intende fornire sempre esattamente un risultato eseguendo una query (sulle relazioni).

Ho detto che voglio menzionare che esiste anche la vita reale ;-). Una relazione uno a uno potrebbe avere senso, ovvero se il conteggio delle colonne della tabella raggiunge un livello critico potrebbe essere più veloce dividere questa tabella. Ma questo tipo di condizioni è davvero raro.

Se non hai davvero bisogno di dividere la tabella, non farlo. Almeno suppongo che nel tuo caso dovresti fare una selezione su due tabelle per ottenere tutti i dati. Questo probabilmente è più lento rispetto alla memorizzazione di tutti in una tabella. E la tua logica di programmazione diventerà almeno un po 'meno leggibile in questo modo.

Edith dice : commentando la normalizzazione: non ho mai visto un database normalizzato al massimo e nessuno lo sta davvero raccomandando come opzione. Ad ogni modo, se non sai esattamente se eventuali colonne verranno normalizzate (ovvero inserite in altre tabelle) in un secondo momento, allora è anche più facile farlo con una tabella invece di "tabella" da uno a uno - "; altra tabella "

Se mantenere

  

informazioni di accesso al giocatore [separate] dalle altre in   informazioni sul gioco (inventario, statistiche,   e stato)

è spesso una questione di normalizzazione. Potresti dare una rapida occhiata a wikipedia ( Third Normal Form , Denormalization ). Il fatto di separarli in più tabelle dipende dai dati archiviati. Espandere la tua domanda renderà questo concreto. I dati che vogliamo archiviare sono:

  • nome utente: nome univoco che consente a un utente di accedere al sistema
  • passwd: password: associata al nome utente che garantisce che l'utente sia unico
  • email: indirizzo email per l'utente; così il gioco può "colpire" l'utente quando non ha giocato di recente
  • carattere: eventualmente separare il nome in gioco per il personaggio
  • status: è il giocatore e / o il personaggio attualmente attivo
  • hitpoint: una statistica di esempio per il personaggio

Potremmo archiviarlo come una singola tabella o come più tabelle.

Esempio di tabella singola

Se i giocatori hanno un singolo personaggio in questo mondo di gioco, allora un singolo tavolo può essere appropriato. Ad esempio,

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

Ciò ti consentirebbe di trovare tutte le informazioni pertinenti su un giocatore con una singola query sul tavolo dei giocatori.

Esempio di tabella multipla

Tuttavia, se si desidera consentire a un singolo giocatore di avere più personaggi diversi, si desidera dividere questi dati su due diversi tavoli. Ad esempio, potresti fare quanto segue:

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

Questo formato richiede join quando si guardano le informazioni sia per il giocatore che per il personaggio; tuttavia, rende meno probabile che l'aggiornamento dei record comporti incoerenze. Quest'ultimo argomento viene in genere utilizzato quando si propongono più tabelle. Ad esempio, se avessi un giocatore (LotRFan) con due personaggi (gollum, frodo) nel formato a tabella singola avresti duplicato il nome utente, la password, i campi email. Se il giocatore volesse cambiare la sua e-mail / password, dovresti modificare entrambi i record. Se è stato modificato un solo record, la tabella diventerà incoerente. Tuttavia, se LotRFan voleva cambiare la sua password nel formato a più tabelle, viene aggiornata una singola riga nella tabella.

Altre considerazioni

L'utilizzo di una singola tabella o di più tabelle dipende dai dati sottostanti. Ciò che non è stato descritto qui ma è stato notato in altre risposte è che le ottimizzazioni spesso prendono due tabelle e le combinano in una singola tabella.

Anche di interesse è conoscere i tipi di modifiche che verranno apportate. Ad esempio, se dovessi scegliere di utilizzare un singolo tavolo per i giocatori che potrebbero avere più personaggi e volessero tenere traccia del numero totale di comandi emessi dal giocatore incrementando un campo nella tabella dei giocatori; ciò comporterebbe l'aggiornamento di più righe nell'esempio della tabella singola.

L'inventario è comunque una relazione molti-a-uno, quindi probabilmente merita una sua tabella (indicizzata almeno sulla sua chiave esterna).

Potresti anche voler avere materiale personale per ogni utente separato da quello pubblico (ovvero ciò che gli altri possono vedere di te). Ciò potrebbe fornire risultati di cache migliori, poiché molte persone vedranno i tuoi attributi pubblici, ma solo tu vedrai i tuoi attributi personali.

Una relazione 1-1 è solo una divisione verticale. Nient'altro. fallo se per qualche motivo non archiverai tutti i dati nella stessa tabella (diciamo per il partizionamento su più server ecc.)

Come puoi vedere, il consenso generale su questo è che "dipende". L'ottimizzazione delle prestazioni / query viene facilmente in mente, come menzionato da @Campbell e altri.

Ma penso che per il tipo di database specifico dell'applicazione che stai costruendo, è meglio iniziare considerando la progettazione complessiva dell'applicazione. Per i database specifici dell'applicazione (al contrario dei database progettati per essere utilizzati con molte app o strumenti di reportistica), il modello di dati "ideale" è probabilmente quello che si adatta meglio al modello di dominio implementato nel codice dell'applicazione.

Non puoi chiamare il tuo design MVC , e io no sapere quale lingua stai usando, ma mi aspetto che tu abbia del codice o delle classi che avvolgono l'accesso ai dati con un modello logico. Il modo in cui visualizzi il design dell'applicazione a questo livello è penso che il miglior indicatore di come dovrebbe essere strutturato il tuo database idealmente .

In generale, ciò significa che un mapping uno-a-uno degli oggetti di dominio alle tabelle del database è il punto migliore da cui iniziare.

Penso che sia meglio illustrare cosa intendo per esempio:

Caso 1: una classe / una tabella

Pensi in termini di una classe Giocatore . Player.authenticate, Player.logged_in ?, Player.status, Player.hi_score, Player.gold_credits. Finché tutte queste sono azioni su un singolo utente o rappresentano gli attributi a valore singolo di un utente, una singola tabella dovrebbe essere il punto di partenza dal punto di vista della progettazione di un'applicazione logica. Singola classe (giocatore), tavolo singolo (giocatori).

Caso 2: classe multipla / una o più tabelle

Forse non mi piace il design di classe Giocatore del coltellino svizzero, ma preferisco pensare in termini di Utente , Inventario , Quadro di valutazione e Account . User.authenticate, User.logged_in ?, User- > account.status, Scoreboard.hi_score (Utente), User- > account.gold_credits.

Probabilmente lo sto facendo perché penso che separare questi concetti nel modello di dominio renderà il mio codice più chiaro e più facile da scrivere. In questo caso, il miglior punto di partenza è una mappatura diretta delle classi di dominio su singole tabelle: Utenti, Inventario, Punteggi, Account ecc.

Rendere "ideale" pratico

Ho inserito alcune parole sopra per indicare che il mapping one-one degli oggetti del dominio sulle tabelle è il design ideale, logico .

Questo è il mio punto di partenza preferito. Cercare di essere troppo intelligente, come mappare più classi su una singola tabella, tende solo a invitare problemi che preferirei evitare (in particolare deadlock e problemi di concorrenza).

Ma non è sempre la fine della storia. Esistono considerazioni pratiche che possono significare che è necessaria un'attività separata per ottimizzare il modello di dati fisici, ad es. problemi di concorrenza / blocco? la dimensione della riga diventa troppo grande? overhead indicizzazione? prestazioni delle query ecc.

Fai attenzione quando pensi alle prestazioni: solo perché può essere una riga in una tabella, probabilmente non significa che venga interrogata una sola volta per ottenere l'intera riga. Immagino che lo scenario di gioco multiutente possa comportare un'intera serie di chiamate per ottenere informazioni diverse sul giocatore in momenti diversi, e il giocatore desidera sempre il "valore corrente" che può essere piuttosto dinamico. Ciò può tradursi in molte chiamate per poche colonne nella tabella ogni volta (nel qual caso, un design a più tabelle potrebbe essere più veloce di un design a tabella singola).

Consiglio di separarli. Non per motivi di database, ma per motivi di sviluppo.

Quando stai codificando il modulo di accesso del tuo giocatore, non vuoi pensare a nessuna delle informazioni sul gioco. E viceversa. Sarà più facile mantenere una separazione delle preoccupazioni se le tabelle sono distinte.

Si riduce al fatto che il modulo di informazioni sul gioco non ha problemi con la tabella di accesso dei giocatori.

Probabilmente anche per metterlo in una tabella in questa situazione poiché le prestazioni della query saranno migliori.

Desideri davvero utilizzare la tabella successiva solo quando ci sarà un elemento di dati duplicati in base al quale dovresti cercare la normalizzazione per ridurre le spese generali di archiviazione dei dati.

Sono d'accordo con Risposta di Thomas Padron-McCarthy :

Risolvere il caso di migliaia di utenti simultanei non è un problema. Fare in modo che le persone giochino il tuo gioco è il problema. Inizia con il design più semplice: esegui il gioco, funzionando, giocando e facilmente espandendo. Se il gioco decolla, allora de-normalizzi.

Vale anche la pena ricordare che gli indici possono essere visti come tabelle separate che contengono una visione più ristretta dei dati.

Le persone sono state in grado di dedurre un disegno utilizzato da Blizzard per World of Warcraft. Sembra che le missioni in cui si trova un giocatore siano memorizzate con il personaggio. per esempio:.

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

Inizialmente potevi partecipare a un massimo di 10 missioni, successivamente è stato ampliato a 25, ad esempio:

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

Questo è in contrasto con un disegno diviso:

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

CREATE TABLE PlayerQuests (
   PlayerID int,
   QuestID int
)

Quest'ultimo è molto più semplice da gestire concettualmente e consente il numero arbitrario di ricerche. D'altra parte devi unirti a Players e PlayerQuests per ottenere tutte queste informazioni.

Vorrei suggerire di mantenere ogni tipo di record nella propria tabella.

Gli accessi al giocatore sono un tipo di record. L'inventario è un altro. Non dovrebbero condividere una tabella poiché la maggior parte delle informazioni non sono condivise. Rendi semplici le tue tabelle separando le informazioni non correlate.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top