【问题标题】:Many-to-Many Relationship Against a Single, Large Table针对单个大表的多对多关系
【发布时间】:2013-08-22 16:59:13
【问题描述】:

我有一个由 5,000 个单元组成的几何图,每个单元都是一个任意多边形。我的应用程序需要保存许多这样的图表。

我已确定我需要使用数据库来针对此地图进行索引查询。加载所有地图数据效率太低,无法快速响应简单查询。

我已将单元格数据添加到数据库中。它的结构相当简单:

CREATE TABLE map_cell (
map_id INT  NOT NULL ,
cell_index INT  NOT NULL ,

...    
PRIMARY KEY (map_id, cell_index)
)

每个映射 5,000 行是相当多的,但是查询应该保持对数百万行的高效,因为可以聚集主连接索引。如果它变得太笨重,可以在 map_id 边界上进行分区。尽管每个地图的行数很多,但此表的可扩展性非常好。

问题在于存储描述哪些单元格彼此相邻的数据。单元格-邻居关系是针对同一张表的多对多关系。每张地图也有非常大量的这种关系。一个规范化的表可能看起来像这样:

CREATE TABLE map_cell_neighbors (
id INT  NOT NULL AUTO INCREMENT ,
map_id INT  NOT NULL ,
cell_index INT  NOT NULL ,
neighbor_index INT ,
...
INDEX IX_neighbors (map_id, cell_index)
)

此表需要一个永远不会在连接中使用的代理键。此外,此表包含重复的条目:如果单元格 0 是单元格 1 的邻居,则单元格 1 始终是单元格 0 的邻居。我可以消除这些条目,但需要一些额外的索引空间:

CREATE TABLE map_cell_neighbors (
id INT  NOT NULL AUTO INCREMENT ,
map_id INT  NOT NULL ,
neighbor1 INT  NOT NULL ,
neighbor2 INT  NOT NULL ,
...
INDEX IX_neighbor1 (map_id, neighbor1),
INDEX IX_neighbor2 (map_id, neighbor2)
)

我不确定哪个会被认为更“规范化”,因为选项 1 包括重复条目(包括复制关系具有的任何属性),而选项 2 是一些非常奇怪的数据库设计,只是感觉不规范.这两种选择都不是非常节省空间。对于 10 个地图,选项 1 使用了 300,000 行,占用了 12M 的文件空间。选项 2 是 150,000 行占用 8M 文件空间。在这两个表上,索引占用的空间比数据多,考虑到数据应该是每行大约 20 个字节,但实际上它在磁盘上占用了 40-50 个字节。

第三个选项根本不会被规范化,但会非常节省空间和行。它涉及在 map_cell 中放置一个 VARBINARY 字段,并在单元表本身中存储一个二进制打包的邻居列表。这将需要每个单元 24-36 个字节,而不是每个关系 40-50 个字节。它还会减少总行数,并且由于聚集的主键,对单元表的查询会非常快。但是,对这些数据执行连接是不可能的。任何递归查询都必须一次完成一个步骤。而且,这只是非常丑陋的数据库设计。

不幸的是,我需要我的应用程序能够很好地扩展,而不是仅使用 50 个地图就遇到 SQL 瓶颈。除非我能想到别的东西,否则后一种选择可能是唯一真正有效的选择。在我将这样一个卑鄙的想法提交给代码之前,我想确保我清楚地查看了所有选项。可能还有另一种我没有想到的设计模式,或者我预见的问题可能并不像看起来那么糟糕。无论哪种方式,我都想在过分深入之前获得其他人的意见。


对这些数据最复杂的查询是路径查找和路径发现。这些将是从特定单元格开始的递归查询,经过多次迭代遍历邻居并收集/比较这些单元格的属性。我很确定我不能在 SQL 中完成所有这些,可能会有一些应用程序代码贯穿始终。我希望能够执行这样大小适中的查询,并在可接受的时间内获得结果,让用户感觉“响应”大约一秒钟。总体目标是防止大表大小导致重复查询或固定深度递归查询花费几秒钟或更长时间。

【问题讨论】:

  • 您的主要目标是空间效率还是执行效率?您预计会对这些数据进行哪些类型的查询? (例子会很好)
  • 我的目标是将两者都保持在可接受的范围内。执行效率很重要,但是当仍然有 AJAX 开销需要处理时,我不想过度优化。如果我们不是在谈论千兆字节,那么空间效率并不是非常重要,但它是服务器空间。我主要关心的是防止大型表缩短 SQL 查询时间。最复杂的查询将是一个递归查询,从单个单元开始并向外分支,然后根据这些单元的属性做出决策。这将通过混合使用 SQL 和应用程序代码来完成。
  • 1) 在您的架构中,外键似乎丢失了 2) 不需要枚举邻居,一个简单的单元到单元 {neighbour1neibour2} 联结表就足够了(显然有两个外键)3)图是无向的吗? (我想是的) 4)循环总是一个问题;至少在 SQL 中。
  • 1) 外键肯定会有所帮助。 2)map_cell_neighbors就是这样一个联结表,减去外键。基本上索引应该是外键。我担心的是当它(快速)达到数百万行时的速度。 3) 是的。这是一个 Voronoi 图,它已经放松以标准化单元大小。 4) 我不确定我是否可以摆脱让每次迭代成为单独查询并在应用程序代码中做出一些决定的需要。我也许可以使用子查询来挖掘到一个固定的深度,但我很清楚 SQL 循环的问题。我宁愿在应用程序代码中做这样的事情。

标签: sql database-design database-schema database-normalization


【解决方案1】:

不确定您使用的是哪个数据库,但您似乎正在重新发明启用空间的数据库已经支持的内容。

例如,如果 SQL Server 是一个选项,您可以将多边形存储为几何类型,使用内置空间索引和符合 OGC 的方法,例如“STContains”、“STCrosses”、“STOverlaps”, “STTouches”。

SQL Server 空间索引在将多边形分解为各种 b 树层之后,还使用曲面细分来索引给定多边形在树索引的给定层接触到哪些相邻单元格。

还有其他主流数据库也支持空间类型,包括MySQL

【讨论】:

  • 这绝对是我一直在寻找的东西。我不知道存在这样的功能。我的应用程序在运行 MySQL 的 Linux Web 服务器上运行。看起来 MySQL 也提供了这样的功能。我会调查细节。
  • 问题:在 WHERE 子句中使用这些空间关系测试真的非常有效吗?我的查询是这样的:“SELECT * FROM map_cell c1 LEFT JOIN map_cell c2 ON c1.map_id = c2.map_id AND Touches(c1.geom, c2.geom)”返回特定单元格的所有邻居。假设表中有 100,000+ 行和 5,000 行满足 c1.map_id=c2.map_id 条件,它是否能够以最佳方式处理这样的查询?
  • 我熟悉 SQL Server 但不熟悉 MySQL,所以我无法具体说明。抽象地说,使用启用空间的数据库引擎来执行该查询将是最优化的方法,而不是尝试执行大型非空间递归。更具体地说,通过更好地阐明您的需求来回答性能问题。是一次性查询吗?它是一次只能为一个单元格执行的动态操作吗?如果地图数据是静态的,您也许可以创建一个表格,其中包含所有接触单元格的笛卡尔积并填充一次(或定期更新)
  • 查询通常是递归的。我经常需要从一个或多个单元位置分支出来,并找到一定范围内的所有单元(比如找到视线范围或移动范围内的所有单元)。地图是固定的。一旦生成,单元格属性可能会改变,但图表的结构永远不会改变。如果两个单元格是邻居,则它们将始终如此。但是,笛卡尔积是每张地图 15,000 个关系,我担心这会在某些时候压倒 SQL。我的 OP 演示了这样一个表,以及它的增长速度。
  • 在快速谷歌之后,MySQL 似乎没有 SQL Server 的“距离”功能。尽管如此...我鼓励您先使用几何类型和功能。在集合中思考(或者在这种情况下,一组单元格可能被量化为一个边界框),并且您可能可以消除递归。此外,虽然笛卡尔积表可能有数千行,但目标是它消除任何运行时计算,并且是按单元格索引的简单查找......但只有在符合您的要求时才使用它。
猜你喜欢
  • 1970-01-01
  • 2010-12-25
  • 1970-01-01
  • 2016-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-23
  • 1970-01-01
相关资源
最近更新 更多