【问题标题】:VERY huge SQL Database: How should the schema look like?非常庞大的 SQL 数据库:架构应该是什么样的?
【发布时间】:2010-11-24 10:45:48
【问题描述】:

我有 2 个文件要导入 MS SQL。第一个文件是 2.2 GB,第二个文件是 24 GB 的数据。 (如果你好奇:这是一个扑克相关的查找表)

将它们导入 MS SQL 不是问题。感谢 SqlBulkCopy,我能够在 10 分钟内导入第一个文件。我的问题是,我不知道实际的表模式应该是什么样子才能让我进行一些非常快速的查询。我的第一次天真的尝试是这样的:

创建表 [dbo].[tblFlopHands](
    [hand_id] [int] IDENTITY(1,1) 非空,
    [flop_index] [smallint] NULL,
    [hand_index] [smallint] NULL,
    [hs1] [真实] NULL,
    [ppot1] [真实] NULL,
    [hs2] [真实] NULL,
    [ppot2] [真实] NULL,
    [hs3] [真实] NULL,
    [ppot3] [真实] NULL,
    [hs4] [真实] NULL,
    [ppot4] [真实] NULL,
    [hs5] [真实] NULL,
    [ppot5] [真实] NULL,
    [hs6] [真实] NULL,
    [ppot6] [真实] NULL,
    [hs7] [真实] NULL,
    [ppot7] [真实] NULL,
    [hs8] [真实] NULL,
    [ppot8] [真实] NULL,
    [hs9] [真实] NULL,
    [ppot9] [真实] NULL,
 约束 [PK_tblFlopHands] 主键集群
(
    [hand_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) 在 [主要] 上

翻牌指数是一个从 1 到 22100 的值(德州扑克中的前 3 张普通牌,52 选 3)。每个翻牌指数都有一个从 1 到 1176 的 hand_index(49 选择 2)。所以这个表总共有 25,989,600 行。

使用我上面的“模式”进行查询大约需要。 25 秒。经过一番谷歌搜索后,我发现 SQL 服务器正在执行表扫描,这显然是一件坏事。我运行了“Database Engine Tuning Advisor”,它建议在 flop_index 列上创建一个索引(有意义)。创建索引后,数据库所需的磁盘空间正好翻了一番! (加上日志 LDF 文件增长了 2.6 GB) 但是在建立索引之后,查询只需要几毫秒。

现在我的问题是,我应该如何以正确的方式做到这一点?我从来没有处理过这么大的数据,我之前创建的数据库就是个笑话。

需要注意的事项:将数据导入 MS SQL 后,将永远不会插入或更新数据,只需选择即可。所以我想知道我是否需要主键?

编辑:我提供更多信息以使我的问题更清楚:

1) 我永远不会使用 hand_id。我只是把它放在那里,因为很久以前有人告诉我我应该总是为每个表创建一个主键。

2) 基本上我只会使用一个查询:

选择 hand_index, hs1, ppot1, hs2, ppot2, hs3, ppot3, hs4, ppot4, hs5, ppot5, hs6, ppot6, hs7, ppot7, hs8, ppot8, hs9, ppot9 WHERE flop_index = 1...22100

此查询将始终返回 1176 行包含我需要的数据。

EDIT2:更具体地说:是的,这是静态数据。我在二进制文件中有这些数据。我编写了一个程序,可以在几毫秒内用我需要的数据查询这个文件。我希望这些数据在数据库中的原因是我希望能够从网络中的不同计算机查询数据,而无需在每台计算机上复制 25 GB。

HS 表示手牌强度,它告诉您当前底牌与翻牌或转牌的手牌强度。 ppot 意味着积极的潜力,这是你的手牌在下一张普通牌发出后领先的机会。 hs1 to 9 是对抗 1 到 9 对手的手力。 ppot 也一样。即时计算 ppot 占用大量 CPU 资源,需要几分钟的时间来计算。我想创建一个扑克分析程序,它给我一个列表,列出所有可能的底牌组合在任何给定的翻牌/转牌与他们的 hs/ppot。

【问题讨论】:

  • 仅供参考,这是一个小型 SQL 数据库,不是一个庞大的数据库;)
  • 嗯,它小。但无论如何,说数据库真的很大是主观的。有很多更大的数据库的例子。只需说出多少 GB 就可以了。
  • 好吧,与 Google 数据库或类似数据库相比,它可能并不庞大,但对于一个宠物项目,我认为它相当庞大 :)
  • @Simon - 这听起来很粗鲁,但这不是我的本意。我希望了解您可能没有提到的任何限制。如果您打算每次都返回相同的记录集/数据,那么将其作为数据库而不是平面文件或硬编码对您有什么价值?

标签: sql database schema


【解决方案1】:

这是一个很常见的问题。当您创建索引时,它可能会减少查询所需的时间,但会增加更新/插入所需的时间,还会增加每条记录所需的磁盘空间量。

您需要为每列确定索引是否为您的查询提供了性能提升,以及它是否保证了对插入/更新性能和磁盘空间利用率的影响。

作为索引的替代方法,您可以使用OLAP cube。如果您的查询正在生成聚合或应用计算,那么您可能需要考虑每晚执行查询并将结果存储在不同的表中。您可以针对较小的表运行更简单的查询,并在对性能影响较小的情况下获得相同的结果。

【讨论】:

    【解决方案2】:

    你如何做你的索引和primkeys取决于。如果您只想分析数据并且您非常确定后续的 DML 命令将只是 SELECT(没有 INSERT),那么删除 PK 应该没问题。事实上,hand_id 列是一个 IDENTITY(自动增量)列,这意味着 SQL Server 无论如何都会管理该值(事实上,您不能将值插入该列,而无需在之前打开 IDENTITY_INSERT 模式的额外麻烦)开始您的 INSERT 语句,IIRC)。

    当然,要警惕此数据库不断变化的需求。如果需要改变,那么你应该考虑约束/索引/键。

    如果将来要考虑数据挖掘,请考虑使用 Microsoft 的 SSAS(分析服务)。

    更新:在阅读了 mayo 的回复后,我同意索引(纯粹是为了速度,而不是强制执行)对于后续查询是可取的(回想一下,索引可以加快读取操作,但通常会使插入/更新花费更长的时间)。由于您的目标是执行单个批量插入,然后执行 SELECT 查询,因此您可以执行批量插入,然后将必要的索引添加到您的数据库中可能是查询中的候选列上。

    【讨论】:

    • 其实我根本不会用hand_id。我创建了 PK 是因为我被教导始终在每个表中创建一个 PK。此外,在我的场景中,一旦插入数据,就永远不会有任何插入或更新。此外,我将始终使用 hand_index 进行查询,因此每个查询将返回 1176 行。那么在 hand_index 列上创建索引后数据库大小翻倍是否正常?我觉得这很奇怪,但如果它是这样工作的,那就顺其自然吧。
    【解决方案3】:

    要回答您关于需要主键的问题 - 仅使用您在问题中提供的信息:

    根据您的表架构,您不妨将其保留在那里。如果您删除该标识列,您也将删除您的聚集索引。您的聚集索引值(4 个字节)作为指针存储在每个非聚集索引行中。通过删除该聚集索引,您会将表保留为堆 - SQL 将为表中的每一行创建一个 8 字节的 RID(行标识符),并将其用作非聚集索引中的指针。因此,在您的情况下,根据您在问题中提供的架构 - 您可能会增加非聚集索引的大小,并最终减慢它们的速度。

    话虽如此 - 基于您可能正在运行但未包含在问题中的查询(及其使用模式) - 将聚集索引评估为非标识列也可能符合要求.

    【讨论】:

      【解决方案4】:

      如果 hs(X) 和 ppot(X) 需要增长到 9 以上,您可以将表拆分为更小的表。

      这就是你所拥有的:

      [hand_id] [int] IDENTITY(1,1) NOT NULL,
          [flop_index] [smallint] NULL,
          [hand_index] [smallint] NULL,
          [hs1] [real] NULL,
          [ppot1] [real] NULL,
          etc...
      

      您可以将它分成 2 个表(如果需要,可以分成 3 个)

      Table hand: (EXAMPLE)
      [hand_id] [int] IDENTITY(1,1) NOT NULL,
          [flop_index] [smallint] NULL,
          [hand_index] [smallint] NULL
      
      
      Table hs_ppot (EXAMPLE)
      [hand_id] [int] IDENTITY(1,1) NOT NULL,
      [hs] [real] NULL,
          [ppot] [real] NULL
      

      然后您可以在每个表中通过 hand_id 引用。只是。

      顺便说一句,什么是 hs 和 ppot?

      【讨论】:

      • hs 表示手力,ppot 表示“积极潜力”
      • 我实际上是在尝试将数据分成多个表,我会告诉你它是如何工作的。不幸的是,我对 SQL 并不感兴趣;)
      【解决方案5】:

      让我先说将所有可能的组合放入数据库中感觉不对。我会在一分钟内了解原因。

      我将从一张名为 Cards 的表格开始。每张可能的卡片都会有 1 条记录,其中包括 Suit、Face value、rank 和是的作为主键的 CardID 字段。还要索引西装和面值。

      如果你想列出所有可能的德州扑克牌,那么我会为 pocketCards(pocketID, pCardID1, pCardID2), flopCards(flopID, fCardID1, fCardID2, fCardID3) 和 TurnAndRiver( turnAndRiverID、turnCardID、riverCardID)。然后是一张带有(handID、pocketID、flopID、turnAndRiverID、handScore)的手牌桌。

      HandScore 是从表格或标量值函数计算出来的字段。

      通过分离这些位,您可以避免大量重复,但您仍然需要担心卡片选择和重叠。

      理想情况下,我会放弃手牌表,并在我为使用这些数据而构建的任何应用程序中计算手牌和得分。

      例如,当客户要求您模拟奥马哈或五张牌抽牌时,将过多的逻辑放入数据库可能会使您难以适应。

      关于您的索引问题,是的,我会使用主键,因为它可以让您快速引用代码中的特定手。

      更新

      响应 OP 的编辑:听起来您在执行此任务时使用了错误的工具。如果您总是要选择完全相同的记录集,那么在数据库中保存数据有什么价值?检查其他选项(例如,平面 XML 文件或代码中的静态 DataSet)。它将为您节省连接时间和为本质上是静态数据运行服务器的开销。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-11-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-31
        • 1970-01-01
        • 1970-01-01
        • 2021-10-30
        相关资源
        最近更新 更多