10

这是我很长一段时间以来一直想知道的事情,但还没有看到真正的(好的)解决方案。这是我想象很多游戏都会遇到的问题,而且我无法轻易想到如何解决(好吧)。欢迎提出想法,但由于这不是一个具体的问题,所以不要费心询问更多细节 - 只是弥补它们!(并解释你的编造)。

好的,所以,许多游戏都有(库存)物品的概念,而且通常有数百种不同种类的物品,所有物品的数据结构通常都非常不同 - 有些物品非常简单(“一块石头”),其他物品可以有疯狂的复杂性或背后的数据(“一本书”、“一个编程的计算机芯片”、“一个装有更多物品的容器”)等。

现在,这样的编程很容易——只要让所有东西都实现一个接口,或者扩展一个抽象的根项。由于编程世界中的对象不必在内部和外部看起来相同,因此任何类型的项目具有多少和什么样的私有字段确实没有问题。

但是当涉及到数据库序列化(二进制序列化当然没问题)时,您将面临一个难题:您将如何在典型的 SQL 数据库中表示它?

我已经看到了一些解决方案的尝试,但没有一个令我满意:

  1. 项目的二进制序列化,数据库只保存一个 ID 和一个 blob。

    • 专业人士:实施大约需要 10 秒。
    • 缺点:基本上牺牲了所有数据库功能,难以维护,几乎不可能重构。
  2. 每个项目类型的表。

    • 优点:干净、灵活。
    • 缺点:随着种类繁多的表出现数百个,每次搜索项目都必须查询它们,因为 SQL 没有表/类型“引用”的概念。
  3. 一张表,其中包含许多并非每个项目都使用的字段。

    • 专业人士:实施大约需要 10 秒,仍然可以搜索。
    • 缺点:浪费空间,性能,从数据库中混淆以判断正在使用的字段。
  4. 一些带有一些“基本配置文件”的表用于存储,其中相似的项目被放在一起,并为不同的数据使用相同的字段。

    • 专业人士:我什么都没有。
    • 缺点:浪费空间,性能,从数据库中混淆以判断正在使用的字段。

你有什么想法?您是否见过另一种效果更好或更差的设计?

4

6 回答 6

4

这取决于您是否需要对这些属性进行排序、过滤、计数或分析。

如果你使用 EAV,那么你会很好地搞砸自己。尝试对 EAV 模式进行报告。

最好的选择是使用表继承:

PRODUCT
id pk
type
att1

PRODUCT_X
id pk fk PRODUCT
att2
att3

PRODUCT_Y
id pk fk PRODUCT
att4
att 5

对于不需要搜索/排序/分析的属性,请使用 blob 或 xml

于 2013-03-01T19:31:50.500 回答
2

我有两种选择:

  1. 基本类型的一个表和每个专业类型“类”的补充表。

    在此模式中,所有“对象”共有的属性存储在一个表中,因此您对游戏中的每个对象都有唯一的记录。对于书籍、容器、可用物品等特殊类型,您可以为这些物品所需的每组独特的属性或关系提供另一个表。因此,每个特殊类型将由两条记录表示:基本对象记录和特定特殊类型表中的补充记录。

    优点:您可以使用数据库的基于列的功能,例如自定义域、检查和 xml 处理;您可以在某些类型上使用更简单的触发器;您的查询在不同的关注点上完全不同。

    缺点:许多对象需要两个插入。

  2. 对特殊类型数据使用“种类”枚举字段和类似 JSONB 的字段。

    这有点像你的#1 或#3,除了一些数据库帮助。Postgres 添加了 JSONB,为您提供了对旧 EAV 模式的改进。其他数据库也有类似的复杂字段类型。在此策略中,您滚动存储在 JSONB 字段中的自己的迷你模式。kind 字段声明了您希望在该 JSONB 字段中找到的内容。

    优点:您可以在查询中提取特殊类型的数据;可以添加检查约束并有一个简单的模式来处理;即使您的数据是异构的,您也可以从索引中受益;您的查询和插入很简单。

    缺点:您在类似 JSONB 的字段中的数据类型非常有限,您必须进行自己的验证。

于 2018-03-28T20:17:35.847 回答
1

是的,设计这样的数据库格式很痛苦。我正在设计一个通知系统并遇到了同样的问题。然而,我的通知系统没有你的那么复杂——它保存的数据最多是 id 和用户名。我当前的解决方案是 1 和 3 的混合 - 我序列化与每个通知不同的数据,并为 2 个用户名使用一列(有些可能有 2 或 1)。我回避方法 2,因为我讨厌那种设计,但它可能只是我。

但是,如果您负担得起,我建议您在 RDBMS 领域之外思考 - 听起来非 RDBMS(尤其是键/值存储的)可能更适合存储这些数据,特别是如果第 1 项和第 2 项不同从每个项目很多。

于 2013-03-01T18:48:52.193 回答
1

我敢肯定这已经在这里被问过一百万次了,但是除了您在问题中讨论的选项之外,您还可以查看非常灵活的 EAV 模式,但它有自己的缺点。

另一种选择是非关系数据库系统。有对象数据库以及各种键/值存储和文档数据库。

通常,当您需要查询灵活属性时,所有这些事情都会在某种程度上分解。然而,这是一个内在的问题。从概念上讲,准确查询非结构化事物的真正含义是什么?

于 2013-03-01T19:36:53.600 回答
1

首先,你真的需要真实数据库的并发性、可扩展性和 ACID 事务吗?除非您正在构建 MMO,否则您的游戏结构无论如何都可能适合内存,因此您可以直接在那里搜索和操作它们。在这种情况下,“数据库”只是序列化对象的存储,您可以将其替换为文件系统。


如果您得出结论(需要数据库),那么关键是从数据管理的角度弄清楚“原子性”的含义。

例如,如果一个游戏项目有一堆属性,但这些属性都没有在数据库级别单独操作(即使它们很可能在应用程序级别),那么它可以被认为是数据中的“原子”管理视角。OTOH,如果需要在其中一些属性上搜索项目,那么您需要一种在数据库中索引它们的好方法,这通常意味着它们必须是单独的字段。

一旦从数据库的角度确定了应该“可见”的属性与应该“不可见”的属性,将后者序列化为 BLOB(或其他),然后忘记它们并专注于构建前者。

这就是乐趣开始的地方,您可能需要使用“以上所有”策略以获得合理的结果。

顺便说一句,一些数据库支持可以进入异构数据结构的“深度”索引。例如,看看 Oracle 的XMLIndex,尽管我怀疑你会在游戏中使用 Oracle。

于 2013-03-01T22:51:59.147 回答
1

您似乎正在尝试针对游戏环境解决此问题,因此也许您可以考虑使用基于组件的方法。我不得不说我个人还没有尝试过,但我已经研究了一段时间,在我看来,类似的东西可以应用。

这个想法是你游戏中的所有实体基本上都是一袋组件。例如,这些组件可以是PositionEnergy或用于您的库存案例Collectable。然后,对于这个Collectable组件,您可以添加自定义字段,例如类别、numItems 等。

当您要呈现库存时,您可以简单地在实体系统中查询具有该Collectable组件的项目。

你怎么能把它保存到数据库中?您可以在它们自己的表中独立定义组件,然后为实体(每个实体也在它们自己的表中)添加一个“组件”列,该列将包含引用这些组件的 ID 数组。这些 ID 实际上就像外键,尽管我知道这并不是您在关系数据库中建模事物的确切方式,但您明白了。

然后,当您在运行时加载实体及其组件时,您可以根据正在加载的组件在其组件包中设置相应的标志,以便您知道该实体具有哪些组件,然后它们将变得可查询。

是一篇关于基于组件的实体系统的有趣读物。

于 2013-03-01T23:19:00.123 回答