【问题标题】:Why does the PHP version run faster than MySQL为什么PHP版本运行速度比MySQL快
【发布时间】:2011-08-27 13:29:42
【问题描述】:

我有两个非常大的表要合并,所以我一直在尝试优化更新以提高速度。我注意到在 PHP 中进行部分更新会显着加快速度,所以我认为这意味着我没有正确执行 MySQL。

我已经简化了问题以尝试缩小范围...

GRID_TABLE POSTCODE_TABLE idNo, lat, lng, nearestPostcode 邮政编码, lat, lng ________________________________ _____________________ 1 57.1 -2.3 - AB12 3BA 56.3 -2.5 2 56.8 -1.9 - AB12 1YA 56.2 -2.3 . . . . . . (200 个条目)(35,000 个条目)

我想使用纬度 (lat) 和经度 (lng) 使用 POSTCODE_TABLE 中最近的邮政编码来更新 GRID_TABLE,以找到离每个网格点最近的邮政编码...

update grid_table set nearestPostcode = (
    select postcode from postcode_table 
    where lat > grid_table.lat -0.0037 and lat < grid_table.lat +0.0037 
        and lng > grid_table.lng -0.0068 and lng < grid_table.lng +0.0068
    order by POW(lat - grid_table.lat,2) + POW((lng - grid_table.lng) *0.546,2) 
    limit 1 
    )

这个想法是“where”子句通过使用索引将集合缩小到几个候选者来加速搜索,然后“order by”子句在这个集合中找到最近的一个。

此 MySQL 更新需要 30 秒,但如果我改为在 PHP 中单独更新每个 GRID_TABLE 行,它会在眨眼间结束。

$queryStg = "select * from grid_table ;";
$sqlQuery1 = mysqli_query($mysqliLink, $queryStg);

while( $sqlRow = mysqli_fetch_assoc( $sqlQuery1 ) ) {

    $idNo = $sqlRow['idNo'];
    $lat = $sqlRow['lat'];
    $lng = $sqlRow['lng'];

    $queryStg = "
        update grid_table
            set nearestPostcode = (
                SELECT postcode
                FROM postcode_table
                where
                    lat > " . ($lat - 0.0037) . " and
                    lat < " . ($lat + 0.0037) . " and
                    lng > " . ($lng - 0.0068) . " and
                    lng < " . ($lng + 0.0068) . "
                ORDER BY
                    POW(lat - $lat, 2) +
                    POW((lng - $lng) * 0.546, 2)
                    ASC
                limit 1
                )
            where idNo= $idNo;
        ";

    $sqlQuery2 = mysqli_query($mysqliLink, $queryStg);

}

MySQL 版本肯定比 PHP 版本快吗?

这是表的 MySQL...

创建表`grid_table`( `idNo` INT(11) NOT NULL AUTO_INCREMENT, `lat` FLOAT(6,4) NOT NULL COMMENT '纬度', `lng` FLOAT(6,4) NOT NULL COMMENT '经度', `nearestPostcode` CHAR(8) NOT NULL, 主键(`idNo`), 索引`lat_lng`(`lat`,`lng`) ) 引擎=MyISAM ROW_FORMAT=默认 AUTO_INCREMENT=30047 创建表`postcode_table`( `postcode` CHAR(8) NOT NULL, `lat` FLOAT(6,4) NOT NULL COMMENT '纬度', `lng` FLOAT(6,4) NOT NULL COMMENT '经度', 主键(`邮政编码`), 索引`lat`(`lat`), 索引`lng`(`lng`), 索引`lat_lng`(`lat`,`lng`) ) 引擎=MyISAM ROW_FORMAT=默认

MySQL 导入文件在这里... https://docs.google.com/leaf?id=0B93lksnTC7_cM2Y2ZDk1Y2YtMGQ3Yy00OTIxLTk0ZDAtZmE2NmQ3YTc1ZWRm&hl=en

(如果您运行 UPDATE,将添加 10 个最近的邮政编码)。

回答后更新...

我跑了这个...

explain extended
 SELECT postcode FROM postcode_table 
 where lat > 57.0 and lat < 57.0074
 and lng > -2.013 and lng < -2
 ORDER BY POW(lat - 57.0, 2) + POW((lng - -2) * 0.546, 2) ASC 

它返回了......

id,select_type,table,type,possible_keys,key,key_len,ref,rows,filtered,Extra 1,SIMPLE,postcode_table,range,lat,lng,lat_lng,lat_lng,8,NULL,65,100.00,使用where;使用文件排序

删除“order by”的原因 -> 速度没有差异。

通过删除'lng'来简化'where'子句,即

grid_table.lat - 0.0037 和 grid_table.lat + 0.0037 之间的纬度 -> 更快:3 秒而不是 30 秒。

使用空间列和索引(见下文)-> 慢得多(190 秒)。不确定我是否正确实现了这一点。

ALTER TABLE `grid_table` ADD COLUMN `coords` POINT NOT NULL;
更新 grid_table set coords = POINT(lat, lng);
ALTER TABLE `grid_table` 添加空间索引 `coords` (`coords`);

ALTER TABLE `postcode_table` ADD COLUMN `coords` POINT NOT NULL;
更新 postcode_table 设置 coords = POINT(lat, lng);
ALTER TABLE `postcode_table` 添加空间索引 `coords` (`coords`);

分析表格grid_table;
优化表grid_table;
分析表 postcode_table;
优化表 postcode_table;
更新 grid_table 设置最近的邮政编码 = ( 从 postcode_table 中选择邮政编码 WHERE MBRContains(GeomFromText(concat( '多边形((', grid_table.lat - 0.0037,'',grid_table.lng - 0.0068,',', grid_table.lat - 0.0037, ' ', grid_table.lng + 0.0068, ', ', grid_table.lat + 0.0037, ' ', grid_table.lng - 0.0068, ', ', grid_table.lat - 0.0037, ' ', grid_table.lng - 0.0068, '))')), postcode_table.coords) 按 POW(lat - grid_table.lat,2) + POW((lng - grid_table.lng) *0.546,2) 排序 限制 1 )

【问题讨论】:

  • 需要在表GRID_TABLE 中的lng 上的索引。查看查询计划(现在和添加此索引之后)。
  • 嗨@ypercube。在 lng 上添加索引。速度没有差别。
  • 查询在 PHP 中看起来非常快,可能是因为它正在被缓存。
  • 嗨@John-Cartwright。你的意思是我读错了?
  • @spiderplant:我认为最好的解决方案是使用空间对象(POINT)和空间索引,而不是存储latlng。请参阅 MySQL 空间扩展dev.mysql.com/doc/refman/5.0/en/spatial-extensions.html

标签: php mysql query-optimization


【解决方案1】:

在您的 MySQL 版本中,您的子查询适用于所有 30000 个 grid_table 记录,无论是在您的 PHP 版本中——它只是一个。当您在外部表 PK 上添加位置时。

我建议您在这里更改更新查询。例如,尝试使其没有子查询,多次更新,如http://dev.mysql.com/doc/refman/5.0/en/update.html

我相信它应该会有所帮助。

类似:

update grid_table, postcode_table
set grid_table.nearestPostcode = postcode_table.postcode
where postcode_table.lat > grid_table.lat - 0.0037
and postcode_table.lat < grid_table.lat + 0.0037 
and postcode_table.lng > grid_table.lng - 0.0068
and lng < grid_table.lng + 0.0068
group by grid_table.idNo
having (POW(lat - grid_table.lat,2) + POW((lng - grid_table.lng) *0.546,2)) = min(POW(lat - grid_table.lat,2) + POW((lng - grid_table.lng) *0.546,2))

这个版本可能会有所帮助,但我不确定。我假设,您的第一个版本中的主要根本问题是对所有记录的子查询。

解释更新,您可以将其“转换”为类似的选择:

explain
select
    *,
    (
        select postcode from postcode_table
        where lat > grid_table.lat -0.0037 and lat < grid_table.lat +0.0037
            and lng > grid_table.lng -0.0068 and lng < grid_table.lng +0.0068
        order by POW(lat - grid_table.lat,2) + POW((lng - grid_table.lng) *0.546,2)
        limit 1
    ) nearestPostcode   
from grid_table

你会看到:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY grid_table  ALL                 224 
2   DEPENDENT SUBQUERY  postcode_table  ALL lat,lng,lat_lng             35605   Using where; Using temporary; Using filesort

但如果是 idNo,我们有:

explain
select
    *,
    (
        select postcode from postcode_table
        where lat > grid_table.lat -0.0037 and lat < grid_table.lat +0.0037
            and lng > grid_table.lng -0.0068 and lng < grid_table.lng +0.0068
        order by POW(lat - grid_table.lat,2) + POW((lng - grid_table.lng) *0.546,2)
        limit 1
    ) nearestPostcode   
from grid_table
where idNo = 1487;

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY grid_table  const   PRIMARY PRIMARY 4   const   1   
2   DEPENDENT SUBQUERY  postcode_table  range   lat,lng,lat_lng lat 4       18  Using where; Using filesort

所以我们有 35605 行 vs ~18*224 (~4000)。

要找到正确的查询,请尝试找到好的选择第一个。

更新

子查询在这里不是根:(所以我认为我们应该尝试一些预先计算的+索引列可能是。目标是避免order by SOMEFUNC()

【讨论】:

  • @update:无法避免 somefunc() 的排序。必须计算给定范围内每个网格点和邮政编码组合之间的距离。查询比php版本慢的主要原因确实是子查询和mysql无法在where子句上使用索引进行相关子查询。如果您删除订单,您也应该看到邮政编码表的完整扫描。距离计算和排序是在非常有限的行集上完成的,这不是根本原因。
  • @piotrm, f..ck... 那么我们需要另一个理论。
  • 嗨@gaRex。我很难理解你的第一个建议。您确定可以将“GROUP BY”与“UPDATE”一起使用吗? dev.mysql.com/doc/refman/5.6/en/update.html
  • 另外 35,000 条记录用于 postcode_table。 grid_table 只有 200 行。
  • @gaRex 感谢您提供有关将更新转换为选择以查看索引使用情况的提示。
【解决方案2】:

查看执行计划以了解需要这么长时间。 http://dev.mysql.com/doc/refman/5.5/en/execution-plan-information.html

【讨论】:

  • 我运行了优化和分析并运行了“解释”。麻烦的是这仅适用于“选择”,并且 php 和 mysql 版本都使用相同的选择。运行在键“lat”上使用“范围”的“解释”报告
【解决方案3】:

我的猜测是,差异是由于您在逐行查询中提供了 $lat 的值,从而在此处为查找节省了大量扫描:-

order by POW(lat - grid_table.lat,2)

就像 Mr47 说的,你可以通过解释 SQL 语句来看到。

【讨论】:

  • 嗨@davek。我不确定我是否理解你。您是否说 PHP 更快是有充分理由的。或者你是说我可以做些什么来加速 MySQL?看到上面运行 EXPLAIN 的结果了吗?
  • 我的意思是,在第一个版本中,当您在子查询中定义变量时,您强制 mysql 执行更多“查找”工作,而在第二个版本中,您提供值(正如 gaRex 所说)。这不是 PHP 与 MySQL(最终都是 SQL)的问题,而是您如何构建查询的问题。您的第二个版本要快得多,因为尽管您要启动多个查询(更多工作)而不是一个,但这不仅仅是通过减少子查询查找来抵消。
猜你喜欢
  • 2012-03-29
  • 2015-10-27
  • 1970-01-01
  • 1970-01-01
  • 2022-01-19
  • 2017-02-03
  • 1970-01-01
  • 2021-11-04
相关资源
最近更新 更多