2

我想优化我的表以加快查询结果。目前我正在使用 MyISAM 而不是 InnoDB。

问题是,第一个邮政编码是 5 个字符长并且不以A字母开头。所有以A字母开头的邮政编码都是 6 或 7 个字符长,大约位于表格记录的中间。您认为我的表中的记录应该按 AZ 顺序排列还是按邮政编码的长度(表顶部列出的字符较少的那些)更适合优化?

或者你还有什么建议?

表结构:

CREATE TABLE `postcodes` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `postcode` varchar(10) NOT NULL,
  `latitude` decimal(25,20) NOT NULL,
  `longitude` decimal(25,20) NOT NULL,
  `fk_areas_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_postcodes_postcode` (`postcode`) USING HASH
) ENGINE=MyISAM AUTO_INCREMENT=1696089 DEFAULT CHARSET=utf8;

询问:

SELECT
                    (@rownum := @rownum + 1) AS No,
                    postcode AS Postcode,
                    latitude AS Latitude,
                    longitude AS Longitude,
                    (
                        (
                            ACOS(SIN((SELECT latitude FROM postcodes WHERE postcode = $postcode) * PI() / 180) * SIN(latitude * PI() / 180) +
                            COS((SELECT latitude FROM postcodes WHERE postcode = $postcode) * PI() / 180) * COS(latitude * PI() / 180) *
                            COS(((SELECT longitude FROM postcodes WHERE postcode = $postcode) - longitude) * PI() / 180)) * 180 / PI()
                        ) * 60 * 1.1515
                    ) AS Distance
                FROM postcodes, (SELECT @rownum := 0) AS No
                HAVING Distance <= 0.5 /*miles*/
                ORDER BY Distance ASC
4

2 回答 2

1

我建议使用具有索引的物化视图。每当您执行诸如COSSIN数据库必须重新计算该功能时,因此您的索引将被忽略。您应该创建一个为您预先计算解决方案的物化视图(我认为在 MySQL 中它们只是视图)。一旦计算出该解决方案,您就可以索引物化视图并针对它进行查询。

显然,在 MySQL 中执行此操作的方法如下:

Create table computed_view  
  --Complex and lengthy sql here  
create index on foo
create index on bar
create index on baz

然后你会这样做:

select * from computed_view where foo = ? and bar = ?

或者更简单的解决方案。在插入之前计算距离,这样您的数据库就只是一个数据存储库(应该处理它的方式)。如果您发现自己在数据库中进行数学计算,那么您就走错了路。将此偏移到 PHP 或您使用的任何语言,然后保留计算值。

于 2012-11-06T16:56:16.380 回答
0

记录的顺序可能没有影响。您可以通过简化查询来优化一些东西,通过预先计算值来优化一些东西,以及通过以最低要求的精度存储数据来优化一些东西。

我一直在对大约 10,000 个随机生成的邮政编码进行一些测试,并且我看到上述所有内容和这个指数的性能提高了(大约)25%:

CREATE INDEX postcodes_ndx ON postcodes(postcode, latitude, longitude);

您的结果将取决于每行中存在哪些其他数据,以及平台和其他参数。

还要考虑利用 MySQL 的空间扩展的可能性。否则,您可能会尝试为每个邮政编码存储一个现成的 UTM 位置(只要您没有覆盖像俄罗斯那么宽的区域),并将第一个表限制为边上三英里平方内的值,以 为中心$postcode。这将立即将检索到的行减少几个数量级,从而成比例地提高查询速度。

我从 JOIN 而不是子选择开始:

SELECT
(@rownum := @rownum + 1) AS No,
A.postcode AS Postcode,
A.latitude AS Latitude,
A.longitude AS Longitude,
ACOS(
        SIN(B.latitude * PI() / 180) * SIN(A.latitude * PI() / 180) +
        COS(B.latitude * PI() / 180) * COS(A.latitude * PI() / 180) *
        COS((B.longitude - A.longitude) * PI() / 180)
) * 180 / PI() * 60 * 1.1515
AS Distance
FROM postcodes AS A,
(SELECT * FROM postcodes WHERE postcode = $postcode) AS B,
(SELECT @rownum := 0) AS No
HAVING Distance <= 0.5 /*miles*/
ORDER BY Distance ASC;

另外,我认为您存储的纬度和经度过于精确。

一旦你有了三位数加六位小数的位置,那大约是一英寸的精度,而你使用的数学公式有一个比这更好的错误。

您还可以通过将纬度和经度存储在辐射度而不是度数中来压缩一些性能;这样您就可以保存大部分PI()/180计算。您可以使用触发器来执行此操作,并使用 and 存储两个额外的列lat_radlng_rad它们需要比 lat 和 lng 多三个小数)。

您还可以预先计算一些值,例如您可以直接乘以的 arccos3958.57而不是180/PI()*60*1.1515.

您还可以在 JOIN 中移动一些三角计算:

SELECT A.postcode AS Postcode,    
       A.latitude AS Latitude,
       A.longitude AS Longitude,
      ACOS(
        sinlat * SIN(A.latitude * PI() / 180)
      + coslat * COS(A.latitude * PI() / 180)
      * COS((B.longitude - A.longitude) * PI() / 180)
      ) * 3958.57 AS Distance
FROM postcodes AS A,
(SELECT latitude, longitude,
 COS(latitude*PI()/180) AS coslat,
 SIN(latitude*PI()/180) AS sinlat
 FROM postcodes WHERE postcode = $postcode
) AS B HAVING Distance <= 0.5
ORDER BY Distance ASC;

最后,您可以删除@rownum计算并将其添加回 PHP:

$rownum = 1;
while($tuple = SQLFetchTuple($exec))
{
    $tuple['No'] = $rownum++;
    ... same code as before...
}

修剪第一张桌子

确实会受益于空间扩展,但我们可以强制第一组邮政编码与第一组相差十分之一度

当然,除非你在赤道,否则这两个距离不会相同——你可以计算出大约两三英里对应的经纬度增量,以获得安全的余量。

SELECT A.postcode AS Postcode,
   A.latitude AS Latitude,
   A.longitude AS Longitude,
   ACOS(
       sinlat * SIN(A.latitude * PI() / 180) +
       coslat * COS(A.latitude * PI() / 180) * COS((B.longitude - A.longitude) * PI() / 180)
   ) * 3958.57 AS Distance
FROM postcodes AS A,
(SELECT latitude, longitude,
  COS(latitude*PI()/180) AS coslat,
  SIN(latitude*PI()/180) AS sinlat
 FROM postcodes WHERE postcode = $postcode) AS B
WHERE
    ABS(A.latitude  - B.latitude ) < 0.1
AND ABS(A.longitude - B.longitude) < 0.1
HAVING Distance <= 0.5

按距离 ASC 排序;

于 2012-11-06T16:37:13.797 回答