对约 225 万行的单表进行选择查询的优化技术?
Posted
技术标签:
【中文标题】对约 225 万行的单表进行选择查询的优化技术?【英文标题】:Optimization techniques for select query on single table with ~2.25M rows? 【发布时间】:2013-08-14 16:23:07 【问题描述】:我有一个在 InnoDB 引擎上运行的名为 squares
的 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 占位符。本质上,此查询的目标是获取表中当前位于由查询中的 4 个坐标限制的 Google 地图窗口的视口中的所有坐标点。
我已限制使用 Google Maps API 运行此查询的缩放级别,以便可以获取的最大行数为 ~5600。随着缩放级别的增加,最终的获取总数会显着降低。
直接在 PHPMyAdmin 中运行这样的示例查询需要 1.40-1.45 秒。这太长了。我已经在ref_coord_lat
和ref_coord_long
上运行标准索引,这使查询时间从约5 秒减少,但这对于最终用户期望及时响应的地图来说仍然太大了。
我的问题很简单:如何进一步优化此表/查询以提高获取结果的速度?
【问题讨论】:
您是否尝试过按 long 和 lat 创建索引? “我已经在 ref_coord_lat 和 ref_coord_long 上运行标准索引,这将查询时间从大约 5 秒缩短” 我的意思是按 (long, lat) 索引 - 即两列上的索引 数据是否适合内存?为您的 Web 服务器购买更多 RAM 并使用 SQL 在启动时播种静态缓存。如果您需要闪电般的速度,请摆脱每个请求的 SQL 网络之旅。 检查 InnoDB 变量innodb_buffer_pool
的大小。如果它是 8 兆字节,这意味着您正在运行一个默认配置,这使得 MySQL 像蜗牛一样运行。增加缓冲池,你会让它运行得更快。
【参考方案1】:
在(lat, long)
上创建复合索引应该会有很大帮助。
但是,正确的解决方案是查看MySQL spatial extensions。空间支持是专门为处理二维数据和针对此类数据的查询而创建的。如果您创建适当的空间索引,您的典型查询性能应该很容易超过 (lat, long)
上的复合索引的性能。
【讨论】:
复合索引最终没有帮助(好吧,我在减少索引大小方面获得了相当的性能)。但是您对 MySQL 空间扩展的看法是正确的。我已经切换(请参阅我自己的答案)并且我已经看到查询速度急剧增加。【参考方案2】:你的结构看起来还不错。 2,25M 行并不多。您的行很小,并且您所做的比较仅针对双精度值。它应该更快。
尝试在您的表上运行ANALYZE
、OPTIMIZE
、CHECK
、REPAIR
命令,以确保您的索引构造正确。
完成此操作后,您应该尝试在系统中进行更深入的调查。 是什么减慢了查询速度?可以是:
磁盘 I/O 内存限制(尝试调整你的 my.cnf,查看优秀的http://www.mysqlperformanceblog.com/) CPU(似乎不太可能) 网络问题使用监控来获取有关您的 sql 缓存、内存使用情况等的数据。 它将帮助您诊断问题。
祝你的项目好运。
【讨论】:
我同意,查询和表是完美的,加快速度的唯一方法是查看系统工具,尤其是缓存... 感谢您的帮助。 MySQL 通过ANALYZE
和OPTIMIZE
告诉我我的结构没问题。我最终切换到 MySQL 空间扩展(请参阅我的帖子正文中的自我回答)。【参考方案3】:
这里的内容最初是由 OP (Antilogical) 作为对问题的编辑而编写的。我将问题的答案部分移至此处,并将其设为社区 wiki。 @Antilogical,如果您想发表自己的答案以获得声誉,请给我留言。
嗯,我修好了。方法如下:
我设法将查询时间从最初的 5 秒缩短到 0.6-0.7 毫秒。我偶然发现了这个问题,“如何为单个查询进一步优化这个 MySQL 表”here。这导致我将我的表从 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 空间扩展时,我现在可以通过使用具有函数 MBRContains()
的最小边界矩形来检查我的新 coordinate
列,该函数是 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 - 我改变了我的查询,而不是选择 * 列,而是选择所有 but 我的新 coordinate
列。
数据库优化?完毕。
【讨论】:
【参考方案4】:虽然不是很优雅,但拆分为多个表(例如每 30 度纬度一个)通常会有所帮助。您的查询很明显哪些表包含所需的点。
另外,使用EXPLAIN 来调查问题。
【讨论】:
另外,与其拆分表,不如对它进行分区。正如您所说,EXPLAIN 输出将更清楚地说明性能问题。以上是关于对约 225 万行的单表进行选择查询的优化技术?的主要内容,如果未能解决你的问题,请参考以下文章