在具有~2.25M行的单个表上进行选择查询的优化技术?

时间:2013-08-13 06:56:22

标签: mysql sql performance select

我有一个名为squares的InnoDB引擎上运行的MySQL表,它有大约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占位符。本质上,此查询的目标是获取表格中当前位于Google地图窗口视口中的所有坐标点,该窗口由查询中的4个坐标限定。

我限制了使用Google Maps API运行此查询的缩放级别,以便可以获取的最大行数 ~5600 。随着缩放级别的增加,合成的提取总量显着减少。

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

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

4 个答案:

答案 0 :(得分:3)

你的结构似乎很好。 2,25M行并没有那么多。你的行很小,你所做的比较只是双值。它应该更快。

尝试在您的表格上运行ANALYZEOPTIMIZECHECKREPAIR命令,以确保正确构建索引。

完成此操作后,您应该尝试深入研究系统。 什么在减慢查询速度?它可以是:

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

祝你的项目好运。

答案 1 :(得分:3)

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

但是,正确的解决方案是查看MySQL spatial extensions。专门创建空间支持来处理针对此类数据的二维数据和查询。如果创建适当的空间索引,则典型的查询性能应该轻松超过(lat, long)上的复合索引的性能。

答案 2 :(得分:2)

此处的内容最初由OP(Antilogical)编写,作为问题的编辑。我将问题的答案部分移到了这里,并将其作为社区维基。 @Antilogical,如果你想发表你自己的答案以获得声誉,请点评我评论。

好吧,我修好了。方法如下:

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

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

ALTER TABLE `squares` ENGINE=MyISAM;

然后,我创建了一个名为coordinate的地理空间列,其中包含一个点对象(它只是ref_coord_latref_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空间扩展时,我现在可以通过使用函数coordinate的最小边界矩形来检查我的新MBRContains()列,该函数是MySQL地理空间扩展集的一部分。

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) - 根据@ N.B.的评论,我将inno_db_buffer_pool的大小从16M增加到256M。
  • 45-50ms - 切换到MyISAM引擎,添加了坐标点列并添加了空间索引
  • 0.6-0.7ms - 我更改了我的查询,以便选择所有我的新coordinate列,而不是选择*列。

数据库优化?完成。

答案 3 :(得分:0)

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

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