5

我有一个在 InnoDB 引擎上运行的 MySQL 表squares,它有大约 2,250,000 行,表结构如下:

`squares` (
   `square_id` int(7) unsigned NOT NULL,
   `ref_coord_lat` double(8,6) NOT NULL,
   `ref_coord_long` double(9,6) NOT NULL,
   PRIMARY KEY (`square_id`),
   KEY `ref_coord_lat` (`ref_coord_lat`),
   KEY `ref_coord_long` (`ref_coord_long`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

第一列square_id包含一个从 0 到 2.25M 的简单递增值,而ref_coord_lat&ref_coord_long分别保存一个点的一组十进制度的纬度和经度坐标。

这是一个只读表。不会添加其他行,唯一需要对其运行的查询如下:

SELECT * FROM `squares` WHERE 
  `ref_coord_lat` BETWEEN :southLat AND :northLat AND 
  `ref_coord_long` BETWEEN :westLong AND :eastLong

...冒号后面的值是 PHP PDO 占位符。本质上,此查询的目标是获取表中当前位于由查询中的 4 个坐标限制的 Google 地图窗口的视口中的所有坐标点。

我已经限制了使用 Google Maps API 运行此查询的缩放级别,因此可以获取的最大行数是~5600。随着缩放级别的增加,最终的获取总数会显着降低。

直接在 PHPMyAdmin 中运行这样的示例查询需要 1.40-1.45 秒。这太长了。我已经在运行标准索引ref_coord_latref_coord_long这使查询时间从大约 5 秒缩短,但这对于最终用户期望及时响应的地图来说仍然太大了。

我的问题很简单:如何进一步优化此表/查询以提高获取结果的速度?

4

4 回答 4

3

创建复合索引(lat, long)应该会有很大帮助。

但是,正确的解决方案是查看MySQL 空间扩展。空间支持是专门为处理二维数据和针对此类数据的查询而创建的。如果您创建适当的空间索引,您的典型查询性能应该很容易超过复合索引的性能(lat, long)

于 2013-08-13T08:56:12.693 回答
3

你的结构看起来还不错。2,25M 行并不多。您的行很小,并且您所做的比较仅针对双精度值。它应该更快。

尝试在您的表上运行ANALYZE, OPTIMIZE, CHECK,REPAIR命令以确保您的索引构造正确。

完成此操作后,您应该尝试在系统中进行更深入的调查。是什么减慢了查询速度?有可能 :

使用监控来获取有关您的 sql 缓存、内存使用情况等的数据。它将帮助您诊断问题。

祝你的项目好运。

于 2013-08-13T07:18:08.443 回答
2

这里的内容最初是由 OP (Antilogical) 编写的,作为对问题的编辑。我将问题的答案部分移至此处,并将其设为社区 wiki。@Antilogical,如果您想发布自己的答案以获得声誉,请给我留言。

嗯,我修好了。就是这样:

我设法将查询时间从最初的 5s 减少到0.6-0.7ms我在这里偶然发现了这个问题,“如何为单个查询进一步优化这个 MySQL 表” 。这导致我将我的表从 InnoDB 切换到 MyISAM 并使用地理空间抽象来表示我的坐标点。

首先,我从 InnoDB 切换到 MyISAM,它更适合 MySQL 空间扩展。

ALTER TABLE `squares` ENGINE=MyISAM;

然后,我创建了一个名为的地理空间列coordinate,其中包含一个点对象(这只是ref_coord_lat和的串联ref_coord_long

UPDATE `squares` SET `coordinate` = GeomFromText(CONCAT('POINT(', `ref_coord_lat`,' ', `ref_coord_long`, ')'));

我添加了一个空间索引coordinate——这极大地提高了查询性能。最初虽然不使用地理空间扩展,但我从数据库中选择字段的查询是:

SELECT * FROM `squares` WHERE `ref_coord_lat` BETWEEN *somecoordinate* AND *somecoordinate* AND `ref_coord_long` BETWEEN *somecoordinate* and *somecoordinate*

这个查询本质上是通过设置每个轴(纬度和经度)的两个限制/条件来模仿边界框。请注意,*当前意味着我的数据库的所有三个字段,我还没有创建coordinate。当我切换到使用 MySQL 空间扩展时,我现在可以通过使用具有MySQL 地理空间扩展集一部分coordinate的函数的最小边界矩形来检查我的新列。MBRContains()

SELECT * FROM `squares` WHERE MBRContains(GeomFromText(POLYGON((*my four bounds now go here in lat,lon pairs*))), `coordinate`);

请注意我如何仍然选择所有字段*?你不需要这样做。该coordinate列仅用作查找值的索引,因此我现在通过下面的此查询选择除该列之外的所有内容,这比直接上面的查询显着提高了速度。

SELECT `square_id`, `ref_coord_lat`, `ref_coord_long` FROM `squares` WHERE MBRContains(GeomFromText(POLYGON((*my four bounds now go here in lat,lon pairs*))), `coordinate`);

速度增加了多个数量级:

  • ~5s - 查询初始squares表的时间(InnoDB,无索引)
  • 1.40-1.45s - 为ref_coord_lat&添加两个索引ref_coord_long
  • 0.9s (900ms) - 然后我发现我的 SELECT 查询中的约束坐标有十几个小数位。我在我的 Javascript 代码中将它们四舍五入为 6(与我的表存储坐标的小数位数相同)。这提供了不错的速度提升。
  • 0.5s (500ms) - 根据@NB 下面的评论,我将大小inno_db_buffer_pool从 16M 增加到 256M。
  • 45-50ms - 切换到 MyISAM 引擎,添加坐标点列并添加空间索引
  • 0.6-0.7ms - 我改变了我的查询,而不是选择 * 列,它选择除了我的新列之外的所有coordinate列。

数据库优化?完毕。

于 2013-09-07T21:48:38.990 回答
0

虽然不是很优雅,但拆分成多个表(比如每 30 度纬度一个)通常会有所帮助。您的查询很明显哪些表包含所需的点。

此外,使用EXPLAIN来调查问题。

于 2013-08-13T07:05:49.723 回答