質問

自由な時間に、データベース バックエンドを備えた小規模なマルチプレイヤー ゲームを書き始めました。私はプレイヤーのログイン情報を他のゲーム内情報 (インベントリ、統計、ステータス) から分離したいと考えていましたが、友人がこれは最善のアイデアではないかもしれないと言いました。

すべてを 1 つのテーブルにまとめたほうがよいでしょうか?

役に立ちましたか?

解決

パフォーマンスを無視することから始め、最も論理的で理解しやすいデータベース設計を作成します。プレーヤーとゲームオブジェクト(剣?チェスの駒?)が概念的に別のものである場合、それらを別のテーブルに配置します。プレーヤーが物を運ぶことができる場合、「物」に外部キーを入れます。 「プレーヤー」を参照するテーブル表。などなど。

次に、何百ものプレーヤーと何千ものものがあり、プレーヤーがゲーム内を走り回ってデータベース検索を必要とすることを行うと、デザインはまだ高速になります。適切なインデックスを追加するだけです。

もちろん、数千の同時プレイヤーを計画している場合、各プレイヤーは毎秒アイテムのインベントリを作成するか、データベース検索(グラフィックエンジンによってレンダリングされる各フレームの検索)の膨大な負荷をかけます。パフォーマンスについて考える。しかし、その場合、テーブルをどのように設計しても、一般的なディスクベースのリレーショナルデータベースはとにかく十分に高速ではありません。

他のヒント

リレーショナルデータベースはセットで機能します。データベースクエリは、結果を何も(「見つかりません」)またはそれ以上に配信します。リレーショナルデータベースは、(リレーションに対して)クエリを実行することにより、常に正確に1つの結果を提供することを意図していません。

実生活もあると言っておきたいと言った;-)。 1対1のリレーションは might 意味があります。つまり、テーブルの列数がクリティカルレベルに達した場合、このテーブルを分割する方が速い場合があります。しかし、この種の条件は本当にまれです。

テーブルを本当に分割する必要がない場合は、分割しないでください。少なくとも、あなたの場合、すべてのデータを取得するには2つ以上のテーブルを選択する必要があると思います。これはおそらく、すべてを1つのテーブルに保存するよりも時間がかかります。そして、そうすることで、プログラミングロジックは少なくとも少し読みにくくなります。

Edithのコメント:正規化についてのコメント:まあ、データベースが最大値に正規化されたのを見たことがありません。とにかく、後で列が正規化されるかどうか(つまり、他のテーブルに配置されるかどうか)が正確にわからない場合は、「table」「one-to-one-one」の代わりに1つのテーブルでこれを行う方が簡単です。他のテーブル"

保持するかどうか

  

他のプレイヤーログイン情報[別]   ゲーム情報(在庫、統計、   およびステータス)

はしばしば正規化の問題です。ウィキペディア(第三標準形非正規化)の定義。これらを複数のテーブルに分割するかどうかは、保存されるデータによって異なります。質問を広げることで、これが具体的になります。格納するデータは次のとおりです。

  • username:ユーザーがシステムにログインできるようにする一意の名前
  • passwd:password:ユーザーが一意であることを保証するユーザー名に関連付けられています
  • email:ユーザーのメールアドレス。そのため、ゲームは「突く」ことができます。ユーザーが最近プレイしていないとき
  • キャラクター:キャラクターのゲーム内名を分ける可能性があります
  • status:現在アクティブなプレイヤーおよび/またはキャラクターです
  • ヒットポイント:キャラクターの統計例

これを単一のテーブルまたは複数のテーブルとして保存できます。

単一テーブルの例

このゲームの世界でプレイヤーが単一のキャラクターを持っている場合、単一のテーブルが適切かもしれません。たとえば、

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

これにより、playersテーブルで1つのクエリを使用して、プレーヤーに関するすべての関連情報を検索できます。

複数のテーブルの例

ただし、1人のプレーヤーに複数の異なるキャラクターを持たせたい場合は、このデータを2つの異なるテーブルに分割します。たとえば、次のことを実行できます。

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

この形式では、プレーヤーとキャラクターの両方の情報を見るときに結合が必要です。ただし、レコードを更新すると矛盾が発生する可能性が低くなります。この後者の引数は、通常、複数のテーブルを提唱するときに使用されます。たとえば、単一テーブル形式の2文字(ゴラム、フロド)のプレーヤー(LotRFan)がある場合、ユーザー名、パスワード、メールフィールドが複製されます。プレーヤーが電子メール/パスワードを変更したい場合、両方のレコードを変更する必要があります。 1つのレコードのみが変更された場合、テーブルは不整合になります。ただし、LotRFanが複数のテーブル形式でパスワードを変更したい場合、テーブルの単一の行が更新されます。

その他の考慮事項

単一のテーブルを使用するか、複数のテーブルを使用するかは、基礎となるデータに依存します。ここでは説明していませんが、他の回答で指摘されているのは、最適化では多くの場合2つのテーブルが使用され、それらを1つのテーブルに結合することです。

また、行われる変更の種類を知ることにも関心があります。たとえば、複数のキャラクターがいる可能性のあるプレイヤーに対して単一のテーブルを使用することを選択し、プレイヤーテーブルのフィールドをインクリメントすることによってプレイヤーによって発行されたコマンドの総数を追跡したい場合。これにより、単一テーブルの例ではより多くの行が更新されます。

インベントリはとにかく多対1の関係なので、おそらく独自のテーブル(少なくとも外部キーでインデックス付けされている)に値するでしょう。

また、各ユーザーに公開のものとは別に個人的なもの(つまり、他の人があなたに見えるもの)を持ちたい場合があります。多くの人があなたの公開属性を見ることになるので、それはより良いキャッシュヒットを提供するかもしれませんが、あなただけがあなたの個人属性を見ます。

1-1の関係は、単なる垂直分割です。他に何もありません。何らかの理由ですべてのデータを同じテーブルに保存しない場合(複数のサーバーにまたがるパーティション分割など)

ご覧のとおり、これについては「状況による」というのが一般的な意見です。@Campbell などが言及しているように、パフォーマンス/クエリの最適化がすぐに思い浮かびます。

ただし、構築しているアプリケーション固有のデータベースの場合は、アプリケーション全体の設計を検討することから始めるのが最善だと思います。(多くのアプリやレポート ツールで使用するように設計されたデータベースではなく) アプリケーション固有のデータベースの場合、「理想的な」データ モデルは、おそらく、アプリケーション コードに実装されているドメイン モデルに最もよくマップされるものです。

自分のデザインを呼び出してはいけない MVC, 、どの言語を使用しているかはわかりませんが、データアクセスを論理モデルでラップするコードまたはクラスがあると思います。このレベルでアプリケーション設計をどう見るかが、データベースがどのようにあるべきかを示す最良の指標だと思います。 理想的には 構造化されること。

一般に、これは、ドメイン オブジェクトをデータベース テーブルに 1 対 1 でマッピングすることから始めるのが最適であることを意味します。

私が何を意味するのかを例として説明するのが最善だと思います。

ケース 1:1クラス/1テーブル

あなたは次のような観点から考えます プレーヤー クラス。Player.authenticate、Player.logged_in?、Player.status、Player.hi_score、Player.gold_credits。これらすべてが単一のユーザーに対するアクションであるか、ユーザーの単一値の属性を表す限り、論理アプリケーション設計の観点からは、単一のテーブルを開始点にする必要があります。単一クラス (プレーヤー)、単一テーブル (プレーヤー)。

ケース 2:複数のクラス / 1 つ以上のテーブル

たぶん私はスイスアーミーナイフが好きではない プレーヤー クラス設計ですが、次の観点から考えることを好みます。 ユーザー, 在庫, スコアボード そして アカウント. 。User.authenticate、User.logged_in?、User->account.status、Scoreboard.hi_score(User)、User->account.gold_credits。

おそらく、ドメイン モデルでこれらの概念を分離すると、コードがより明確になり、記述が容易になると考えているため、これを行っています。この場合、最良の開始点は、ドメイン クラスを個々のテーブルに直接マッピングすることです。ユーザー、インベントリ、スコア、アカウントなど

「理想」を現実にする

ドメイン オブジェクトとテーブルの 1 対 1 マッピングが、 理想的な、論理的な デザイン。

それが私の好ましい出発点です。複数のクラスを 1 つのテーブルにマッピングし直すなど、最初から賢くなりすぎると、避けたい問題 (特にデッドロックや同時実行の問題) を招くだけになる傾向があります。

しかし、必ずしもそれで話が終わるわけではありません。物理データ モデルを最適化するための別のアクティビティが必要になる可能性がある実際的な考慮事項があります。同時実行/ロックの問題?行サイズが大きすぎますか?インデックス作成のオーバーヘッド?クエリパフォーマンスなど

ただし、パフォーマンスについて考えるときは注意してください。1 つのテーブル内の 1 行である可能性があるからといって、行全体を取得するために 1 回だけクエリを実行するという意味ではないでしょう。マルチユーザー ゲームのシナリオでは、さまざまな時点でさまざまなプレイヤー情報を取得するための一連の呼び出しが含まれる可能性があり、プレイヤーは常に「現在の値」を必要としますが、これは非常に動的である可能性があります。これは、テーブル内のわずか数列に対して毎回何度も呼び出しを行うことになる可能性があります (その場合、複数テーブル設計の方が単一テーブル設計よりも高速になる可能性があります)。

それらを分離することをお勧めします。データベースの理由ではなく、開発の理由です。

プレーヤーのログインモジュールをコーディングする場合、ゲーム情報については考えたくないでしょう。そして、ビザの逆。テーブルが異なる場合、関心の分離を維持するのが簡単になります。

要するに、あなたのゲーム情報モジュールは、プレイヤーのログインテーブルを取り巻くビジネス上の問題を抱えていないという事実に帰着します。

おそらく、クエリのパフォーマンスが向上するため、この状況でも1つのテーブルに配置する必要があります。

実際には、データストレージのオーバーヘッドを削減するために正規化を検討する必要がある重複データの要素がある場合にのみ、次のテーブルを使用します。

i Thomas Padron-McCarthyの回答

数千の同時ユーザーの場合の解決はあなたの問題ではありません。ユーザーにゲームをプレイしてもらうことが問題です。最もシンプルなデザインから始めましょう-ゲームを完成させ、機能し、プレイ可能で、簡単に拡張できます。ゲームが離陸したら、非正規化します。

インデックスは、データのより狭いビューを含む個別のテーブルとして見ることができることにも言及する価値があります。

人々は、BlizzardがWorld of Warcraftで使用しているデザインを推測できました。プレイヤーが行っているクエストはキャラクターと一緒に保存されているようです。例:

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

最初は最大10個のクエストに参加できましたが、後で25に拡張されました。例:

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

これは、分割デザインとは対照的です:

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

CREATE TABLE PlayerQuests (
   PlayerID int,
   QuestID int
)

後者は概念的にはるかに扱いやすく、任意の数のクエストを許可します。一方、すべての情報を取得するには、プレイヤーとプレイヤークエストに参加する必要があります。

各レコードタイプを独自のテーブルに保持することをお勧めします。

プレーヤーログインは、レコードの一種です。インベントリは別です。ほとんどの情報は共有されないため、テーブルを共有しないでください。無関係な情報を分離することで、テーブルをシンプルに保ちます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top