在我的空闲时间里,我开始编写一个带有数据库后端的小型多人游戏。我当时希望将玩家登录信息与其他游戏信息(库存,统计数据和状态)分开,而朋友提出的这可能不是最好的主意。
将所有内容整合在一张桌子中会更好吗?
答案 0 :(得分:10)
首先忽略性能,然后创建最符合逻辑且易于理解的数据库设计。如果玩家和游戏对象(剑?棋子?)在概念上是分开的东西,那么将它们放在单独的表中。如果玩家可以携带东西,则将外键放在引用“玩家”表的“事物”表中。等等。
然后,当你有数百名玩家和成千上万的东西,并且玩家在游戏中跑来跑去并做需要数据库搜索的事情时,那么你的设计仍然足够快,如果你只需添加适当的索引。
当然,如果你计划成千上万的同时玩家,他们每个人每秒都会清点他的东西,或者可能是其他一些大量的数据库搜索(搜索图形引擎渲染的每一帧?)那么你需要考虑表现。但在这种情况下,无论你如何设计表,一个典型的基于磁盘的关系数据库都不会足够快。
答案 1 :(得分:5)
关系数据库在集合上工作,数据库查询可以提供任何(“未找到”)或更多结果。关系数据库无意通过执行查询(关系)来始终提供一个结果。
说我想提一下现实生活;-)。一对一关系可能有意义,即如果表的列数达到临界级别,则拆分此表可能会更快。但这种情况确实很少见。
如果你真的不需要拆分表,就不要这样做。至少我假设在你的情况下你必须选择两个表来获取所有数据。这可能比将所有内容存储在一个表中要慢。而且,通过这样做,您的编程逻辑至少会降低可读性。
伊迪丝说:评论规范化:好吧,我从来没有看到数据库规范化为最大值,没有人真的推荐这个作为选项。无论如何,如果你不确切知道以后是否会对任何列进行规范化(即放入其他表),那么使用一个表而不是“table”-one-to-one-“其他表”也更容易做到这一点“
答案 2 :(得分:3)
是否保留
玩家登录信息[另外]与其他人分开 游戏信息(库存,统计, 和状态)
通常是规范化的问题。您可能需要快速查看维基百科(Third Normal Form,Denormalization)定义。是否将这些表分成多个表取决于存储的数据。扩展你的问题将使这个具体。我们要存储的数据是:
我们可以将其存储为单个表或多个表。
单表示例
如果玩家在这个游戏世界中只有一个角色,那么单个表可能是合适的。例如,
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);
在查看播放器和角色的信息时,此格式确实需要连接;但是,它使更新记录不太可能导致不一致。后一个参数通常在提倡多个表时使用。例如,如果您有一个具有两个字符(gollum,frodo)的播放器(LotRFan),那么您将拥有重复的用户名,密码和电子邮件字段。如果玩家想要更改他的电子邮件/密码,则需要修改这两个记录。如果只修改了一条记录,则该表将变得不一致。但是,如果LotRFan想要以多表格式更改密码,则表格中的一行会更新。
其他注意事项
是使用单个表还是多个表取决于基础数据。这里没有描述但在其他答案中已经注意到的是,优化通常需要两个表并将它们组合成一个表。
同样感兴趣的是了解将要进行的更改类型。例如,如果您选择对可能有多个角色的玩家使用单个表,并希望通过增加玩家表中的字段来跟踪玩家发出的命令总数;这将导致在单个表示例中更新更多行。
答案 3 :(得分:2)
无论如何,库存是多对一的关系,所以可能应该拥有自己的表(至少在其外键上索引)。
您可能还希望将每个用户的个人资料与公共资源分开(即,其他人可以看到您的内容)。这可能会提供更好的缓存命中率,因为很多人会看到你的公共属性,但只有你看到你的个人属性。
答案 4 :(得分:1)
1-1关系只是一个垂直分裂。没有其他的。如果由于某种原因您不会将所有数据存储在同一个表中(比如跨多个服务器进行分区等),请执行此操作
答案 5 :(得分:1)
正如您所看到的,对此的普遍共识是“它取决于”。很容易想到性能/查询优化 - 正如@Campbell和其他人所提到的那样。
但我认为对于您正在构建的特定于应用程序的数据库,最好从考虑整体应用程序设计开始。对于特定于应用程序的数据库(与设计用于许多应用程序或报告工具的数据库相对),“理想”数据模型可能是最适合应用程序代码中实现的域模型的数据模型。
您可能无法调用您的设计MVC,而且我不知道您使用的是哪种语言,但我希望您有一些代码或类可以使用逻辑模型包装数据。您如何在此级别查看应用程序设计是我认为最佳指标,表明您的数据库应该理想的结构。
通常,这意味着域对象到数据库表的一对一映射是最佳起点。
我认为最好用例子来说明我的意思:
您认为就播放器类而言。 Player.authenticate,Player.logged_in?,Player.status,Player.hi_score,Player.gold_credits。 只要所有这些操作都是针对单个用户的操作,或者代表用户的单值属性,那么从逻辑应用程序设计角度来看,单个表应该是您的起点。单人(球员),单人桌(球员)。
也许我不喜欢瑞士军刀播放器课程设计,但更喜欢用用户,广告资源来思考,记分板和帐户。 User.authenticate,User.logged_in ?, User-> account.status,Scoreboard.hi_score(User),User-> account.gold_credits。
我可能这样做是因为我认为在域模型中分离这些概念会使我的代码更清晰,更容易编写。在这种情况下,最好的起点是将域类直接映射到各个表:用户,库存,分数,帐户等。
我在上面说了几句话,表明域对象与表的一对一映射是理想的逻辑设计。
这是我的首选起点。试图过于聪明 - 比如将多个类映射回单个表 - 往往只会引起我宁愿避免的麻烦(尤其是死锁和并发问题)。
但这并不总是故事的结局。实际考虑可能意味着需要单独的活动来优化物理数据模型,例如,并发/锁定问题?行大小太大了?索引开销?查询表现等。
在考虑性能时要小心:因为它可能是一个表中的一行,可能并不意味着它只被查询一次以获得整行。我想多用户游戏场景可能涉及一系列调用以在不同时间获得不同的玩家信息,并且玩家总是想要可能非常动态的“当前值”。这可能会导致每次调用表中只有几列(在这种情况下,多表设计可能比单表设计更快)。
答案 6 :(得分:1)
我建议将它们分开。不是出于任何数据库原因,而是出于开发原因。
当您编写播放器登录模块时,您不想考虑任何游戏信息。反之亦然。如果表格不同,将更容易保持关注点分离。
归结为这样一个事实,即您的游戏信息模块在玩家登录表中没有任何商业信息。
答案 7 :(得分:0)
在这种情况下将它放在一个表中也可能是因为您的查询性能会更好。
当你有一个重复数据元素时,你真的只想使用下一个表,你应该通过它来规范化以减少数据存储开销。
答案 8 :(得分:0)
我建议您将每种记录类型保存在自己的表中。
玩家登录是一种记录。库存是另一个。由于大多数信息不共享,因此不应共享表格。通过分离出不相关的信息来保持简单的表格。
答案 9 :(得分:0)
我同意Thomas Padron-McCarthy's answer:
解决数千个并发用户的情况不是您的问题。让人们玩你的游戏就是问题所在。从最简单的设计开始 - 让游戏完成,工作,可玩,并且易于扩展。如果游戏起飞,那么你会去标准化。
还值得一提的是,索引可以看作是包含较窄数据视图的单独表。
人们已经能够推断出暴雪对魔兽世界使用的设计。似乎玩家所在的任务与角色一起存储。 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才能获得所有信息。