在空闲时间,我开始编写一个带有数据库后端的小型多人游戏。我想将玩家登录信息与游戏中的其他信息(库存、统计数据和状态)分开,一位朋友提出这可能不是最好的主意。

将所有内容集中在一张表中会更好吗?

有帮助吗?

解决方案

首先忽略性能,然后创建最符合逻辑且易于理解的数据库设计。如果玩家和游戏对象(剑?棋子?)在概念上是分开的东西,那么将它们放在单独的表中。如果玩家可以携带东西,你可以在“东西”中放入一个外键。引用“玩家”的表格表。等等。

然后,当你有数百名玩家和成千上万的东西,并且玩家在游戏中跑来跑去并做需要数据库搜索的事情时,那么你的设计仍然足够快,如果你只需添加适当的索引。

当然,如果你计划成千上万的同时玩家,他们每个人每秒都会清点他的东西,或者可能是其他一些大量的数据库搜索(搜索图形引擎渲染的每一帧?)那么你需要考虑表现。但在这种情况下,无论你如何设计表,一个典型的基于磁盘的关系数据库都不会足够快。

其他提示

关系数据库在集合上工作,数据库查询在那里不提供(“未找到”)或更多结果。关系数据库无意通过执行查询(关系)来始终提供一个结果。

说我想提一下现实生活;-)。一对一关系可能有意义,即如果表的列数达到临界级别,则拆分此表可能会更快。但这种情况确实很少见。

如果你真的不需要拆分表,就不要这样做。至少我假设在你的情况下你必须选择两个表来获取所有数据。这可能比将所有内容存储在一个表中要慢。而且,通过这样做,您的编程逻辑至少会降低可读性。

伊迪丝说:评论规范化:好吧,我从来没有看到数据库规范化到它的最大值,没有人真的推荐这个作为选项。无论如何,如果你不确切知道以后是否会对任何列进行规范化(即放入其他表格),那么使用一个表而不是“表格”-one-to-one-“其他表格“

是否保留

玩家登录信息[分开]与其他信息分开 游戏信息(库存、统计数据、 和状态)

通常是一个标准化问题。您可能想快速浏览一下维基百科(第三范式, 非规范化) 定义。是否将它们分成多个表取决于存储的数据。扩大你的问题将使这一点具体化。我们要存储的数据是:

  • 用户名:允许用户登录系统的唯一名称
  • 密码:密码:与保证用户唯一的用户名相关联
  • 电子邮件:用户的电子邮件地址;这样游戏就可以在用户最近没有玩的时候“戳”他们
  • 特点:角色可能有单独的游戏名称
  • 地位:玩家和/或角色当前是否处于活动状态
  • 命中率:角色统计数据示例

我们可以将其存储为单个表或多个表。

单表示例

如果玩家在这个游戏世界中只有一个角色,那么单个桌子可能是合适的。例如,

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

这将允许您通过在玩家表上进行单个查询来找到有关玩家的所有相关信息。

多表示例

但是,如果您希望允许单个玩家拥有多个不同的角色,您可能需要将此数据拆分到两个不同的表中。例如,您可以执行以下操作:

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

当查看玩家和角色的信息时,这种格式确实需要连接;然而,它使得更新记录不太可能导致不一致。后一个参数通常在提倡使用多个表时使用。例如,如果您的玩家 (LotRFan) 在单表格式中具有两个字符(gollum、frodo),您的用户名、密码、电子邮件字段将会重复。如果玩家想要更改他的电子邮件/密码,您需要修改这两条记录。如果只修改了一条记录,表将变得不一致。但是,如果 LotRFan 想要以多表格式更改其密码,则表中的一行将被更新。

其他考虑因素

使用单表还是多表取决于底层数据。这里没有描述但在其他答案中注意到的是,优化通常会采用两个表并将它们组合成一个表。

同样有趣的是了解将要进行的更改类型。例如,如果您选择对可能拥有多个角色的玩家使用单个表,并希望通过增加玩家表中的字段来跟踪玩家发出的命令总数;这将导致在单个表示例中更新更多行。

无论如何,库存是多对一的关系,所以可能应该拥有自己的表(至少在其外键上索引)。

您可能还希望将每个用户的个人资料与公共资源分开(即,其他人可以看到您的内容)。这可能会提供更好的缓存命中率,因为很多人会看到你的公共属性,但只有你看到你的个人属性。

1-1关系只是一个垂直分裂。没有其他的。如果由于某种原因您不会将所有数据存储在同一个表中(比如跨多个服务器进行分区等),请执行此操作

如您所见,对此的普遍共识是“它取决于”。很容易想到性能/查询优化 - 正如@Campbell和其他人所提到的那样。

但我认为对于您正在构建的特定于应用程序的数据库,最好从考虑整体应用程序设计开始。对于特定于应用程序的数据库(与设计用于许多应用程序或报告工具的数据库相对),“理想”数据模型可能是最适合应用程序代码中实现的域模型的数据模型。

您可能无法将您的设计 MVC ,而我不会知道你正在使用什么语言,但我希望你有一些代码或类用逻辑模型包装数据访问。您如何在此级别查看应用程序设计是我认为最佳指标,表明您的数据库应该理想的结构。

通常,这意味着域对象到数据库表的一对一映射是最佳起点。

我认为最好用例子来说明我的意思:

案例1:一个班级/一个班级

您认为就播放器类而言。 Player.authenticate,Player.logged_in?,Player.status,Player.hi_score,Player.gold_credits。 只要所有这些操作都是针对单个用户的操作,或者代表用户的单值属性,那么从逻辑应用程序设计角度来看,单个表应该是您的起点。单人(球员),单人桌(球员)。

案例2:多个班级/一个或多个表格

也许我不喜欢瑞士军刀播放器课程设计,但更喜欢用用户广告资源来思考,记分板帐户。 User.authenticate,User.logged_in ?, User-> account.status,Scoreboard.hi_score(User),User-> account.gold_credits。

我可能这样做是因为我认为在域模型中分离这些概念会使我的代码更清晰,更容易编写。在这种情况下,最好的起点是将域类直接映射到各个表:用户,库存,分数,帐户等。

制作'理想'实用

我在上面说了几句话,表明域对象与表的一对一映射是理想的逻辑设计。

这是我的首选起点。试图过于聪明 - 比如将多个类映射回单个表 - 往往只会引起我宁愿避免的麻烦(尤其是死锁和并发问题)。

但这并不总是故事的结局。实际考虑可能意味着需要单独的活动来优化物理数据模型,例如,并发/锁定问题?行大小太大了?索引开销?查询表现等。

在考虑性能时要小心:因为它可能是一个表中的一行,可能并不意味着它只被查询一次以获得整行。我想多用户游戏场景可能涉及一系列调用以在不同时间获得不同的玩家信息,并且玩家总是想要可能非常动态的“当前值”。这可能会导致每次调用表中只有几列(在这种情况下,多表设计可能比单表设计更快)。

我建议将它们分开。不是出于任何数据库原因,而是出于开发原因。

当您编写播放器登录模块时,您不想考虑任何游戏信息。反之亦然。如果表格不同,将更容易保持关注点分离。

归结为这样一个事实,即您的游戏信息模块在玩家登录表中没有任何商业信息。

在这种情况下将它放在一个表中也可能是因为您的查询性能会更好。

当你有一个重复数据元素时,你真的只想使用下一个表,你应该通过它来规范化以减少数据存储开销。

我同意 Thomas Padron-McCarthy的回答

解决数千个并发用户的情况不是您的问题。让人们玩你的游戏就是问题所在。从最简单的设计开始 - 让游戏完成,工作,可玩,并且易于扩展。如果游戏起飞,那么你会去标准化。

还值得一提的是,索引可以看作是包含较窄数据视图的单独表。

人们已经能够推断出暴雪对魔兽世界使用的设计。似乎玩家所在的任务与角色一起存储。 e.g:

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
)

后者在概念上更容易处理,它允许任意数量的任务。另一方面,您必须加入Players和PlayerQuest才能获得所有信息。

我建议您将每种记录类型保存在自己的表中。

玩家登录是一种记录。库存是另一个。由于大多数信息不共享,因此不应共享表格。通过分离出不相关的信息来保持简单的表格。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top