【问题标题】:Optimizing a Geo-Distance Related MySQL Query优化与地理距离相关的 MySQL 查询
【发布时间】:2015-06-01 15:34:43
【问题描述】:

我有以下 MySQL 查询:

SELECT 
a.*, 
( 3959 * acos( cos( radians('47.3909') ) * cos( radians( a.lat ) ) * cos( radians( a.lng ) - radians('-122.2637') ) + sin( radians('47.3909') ) * sin( radians( a.lat ) ) ) ) AS distance 
FROM zip_codes AS a 
ORDER BY distance ASC 
LIMIT 1;

这将为我提供zip_codes 表中最接近我指定坐标的邮政编码。

但是,这运行得很慢! 1秒左右。所有类似的查询也运行大约 1 秒。我想知道是否可以优化我的表结构或查询以缩短查询时间。

这是我的zip_codes 表的架构:

CREATE TABLE `zip_codes` (
  `zip` varchar(10) COLLATE utf8_unicode_ci NOT NULL,
  `city` varchar(64) CHARACTER SET utf8 DEFAULT NULL,
  `state` char(2) CHARACTER SET utf8 DEFAULT NULL,
  `type` char(1) CHARACTER SET utf8 DEFAULT NULL,
  `timezone` int(11) DEFAULT NULL,
  `lat` varchar(255) CHARACTER SET utf8 DEFAULT NULL,
  `lng` varchar(255) CHARACTER SET utf8 DEFAULT NULL,
  `country` varchar(2) COLLATE utf8_unicode_ci DEFAULT '',
  PRIMARY KEY (`zip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=COMPACT;

更新我将 latlng 的字段类型更改为 DECIMAL,现在查询实际上需要更长的时间,令人惊讶!

【问题讨论】:

  • 您已更改数据类型,但我怀疑您仍在隐式地将变量转换为字符串。另外,只是好奇...如果您订购 ( 3959 * acos( cos( radians('47.3909') ) * cos( radians( a.lat ) ) * cos( radians( a.lng ) - radians('-122.2637') ) + sin( radians('47.3909') ) * sin( radians( a.lat ) ) ) )) 会发生什么
  • 当我更改 ORDER BY @Strawberry 时的完全相同的性能
  • 你能与邮政编码表数据共享一个 sql 转储或 sqlfiddle 吗?我知道我可以自己制作它,我只是想要一个快速测试游乐场来尝试实现我的一个想法。谢谢
  • @Alexey - canary-file-drop.s3.amazonaws.com/zip_codes.sql.zip - 这是一个 SQL 转储。
  • 谢谢,将尝试在本地测试一些东西。如果它比您最初的解决方案效果更好,我将作为答案发布

标签: mysql indexing coordinates geospatial


【解决方案1】:

好的,所以我必须警告你,这绝对不是一个完美的解决方案,并且有以下缺点:

  1. 它不适用于美国的所有点。例如,如果您在阿拉斯加某处选择一个点,距离表中的每个邮政编码中心都超过 50 公里,则它不会返回任何内容

  2. 需要 MyISAM 存储引擎

  3. in 包括硬编码值(见第 1 点约 50 公里)。它不完全是 50 公里,并且与经度不同。

先决条件:

鉴于您发送的转储,您应该启动以下查询:

ALTER TABLE `zip_codes` ENGINE=MYISAM; -- changing your storage engine to MyISAM. It supports spatial indexes in MySQL
ALTER TABLE `zip_codes` ADD `pt` POINT NOT NULL; -- adding POINT() spatial datatype for zip cetner. Eventually, you may remove the old lat/lng decimal columns
ALTER TABLE `zip_codes` ADD `region` POLYGON NOT NULL; -- adding a rectangle over the center of the zip code. See below, this is something to utilize spatial index later in ST_Intersects function

// update the new columns with respective values
UPDATE `zip_codes` SET `pt` = POINT(lat,lng);
UPDATE `zip_codes` SET `region` = GEOMFROMTEXT(CONCAT('POLYGON((',lat-0.5,' ',lng-0.5,', ',lat+0.5,' ',lng-0.5,', ',lat+0.5,' ',lng+0.5,', ',lat-0.5,' ',lng+0.5,', ',lat-0.5,' ',lng-0.5,'))')); -- 0.5 is 0.5 degrees hardcode. There is a better approach and it's better to write a MySQL function that will increase the MBR with certain step until there is intersection (see my point #1 above, this is the best solution)

// create indexes on the newly created columns
ALTER TABLE `zip_codes` ADD SPATIAL INDEX(`region`);
ALTER TABLE `zip_codes` ADD SPATIAL INDEX(`pt`);

新查询

SELECT SQL_NO_CACHE zip,ST_Distance(`pt`,POINT('47.3909','-122.2637')) AS dst
FROM `zip_codes`
WHERE ST_Intersects(POINT('47.3909','-122.2637'),`region`)
ORDER BY `dst`
LIMIT 1;

在我的机器上大约需要 0.011 秒,这要好得多。

但同样,请参阅上面更新声明附近的评论,您应该考虑两件事:

  1. 编写一个函数,该函数将以 0.5 度的步长(例如)增加最小边界矩形,直到出现交叉点
  2. 迁移到 PostgreSQL + PostGIS 扩展。如果您处理需要空间扩展的大量记录,则功能会更加强大

【讨论】:

  • 我会玩这个并且会发布结果。谢谢。
猜你喜欢
  • 2016-11-24
  • 1970-01-01
  • 1970-01-01
  • 2012-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-28
  • 1970-01-01
相关资源
最近更新 更多