【问题标题】:Strange slow queries with MySQLMySQL 奇怪的慢查询
【发布时间】:2014-04-12 11:55:20
【问题描述】:

我有表 A,其中包含 IP 范围(列 startIpNum、endIpNum、locId)和表 A_location(列 locaId 和其他不重要的列)。有以下索引 - A 上的 startIpNum 和 endIpNum,以及 A_location 上的 locId。

问题是有时查询的执行速度非常慢。下面是一个 mysql-slow 日志文件,其中包含两个查询,其中两个查询都不返回。

# Time: 140001 21:18:45    
# User@Host: root[root] @ localhost [127.0.0.1]
# Query_time: 0.023001  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0
SET timestamp=1394367480;
SELECT * FROM A_location AS location, (SELECT * FROM A WHERE (3998482191 BETWEEN startIpNum AND endIpNum) ORDER BY startIpNum DESC LIMIT 1) AS blocks WHERE location.locId = blocks.locId;

# Time: 140309 21:18:45
# User@Host: root[root] @ localhost [127.0.0.1]
# Query_time: 54.893140  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0
SET timestamp=1394367525;
SELECT * FROM A_location AS location, (SELECT * FROM A WHERE (2463400155 BETWEEN startIpNum AND endIpNum) ORDER BY startIpNum DESC LIMIT 1) AS blocks WHERE location.locId = blocks.locId;

这种行为的原因可能是什么?

解释结果:

更新: 问题解决了,最后查询

ALTER TABLE A ORDER BY startIpNum ASC;
SELECT A_location.* FROM A_location AS location,
(SELECT A.* FROM A as blocks,
(SELECT * FROM A WHERE startIpNum < 24465138 ORDER BY startIpNum DESC LIMIT 1) AS startipnumquery
WHERE blocks.startIpNum = startipnumquery.startIpNum AND blocks.endIpNum > 24465138
ORDER BY blocks.endIpNum ASC LIMIT 1) as subresult
WHERE location.locId = subresult.locId;

【问题讨论】:

  • 如果 SQL 查询在 MySQL 中运行缓慢,请使用 EXPLAIN 来了解它们是如何执行的
  • 如果我没记错的话(已经有一段时间了!)子选择是在父查询的每一行中执行的。所以最坏的情况是你正在做一个 O(n^2) 操作,其中内部操作包括一个排序,这是可以预见的昂贵的。正如 Mark 所说,EXPLAIN 应该让您更好地了解到底发生了什么。
  • 添加了解释截图
  • 所以,基本上,如果EXPLAIN部分中id=2下的查询使用filesort,速度很快,但是如果使用WHERE,速度很慢。
  • @Polynomial, subselect,在第一种情况下执行,非常快。我还提供了 EXPLAIN 的

标签: php mysql sql performance geoip


【解决方案1】:

打开分析功能,像福尔摩斯一样:

mysql> set profiling = 1;

mysql> select * from A;

mysql> show profiles;

找到您的查询(可能 id 为 1)并执行:

mysql> show profile for query 1;

【讨论】:

    【解决方案2】:

    我不太确定您是要获取具有最高 startipnum 的位置,还是所有位置和每个位置的最高 startipnum。

    您当前的查询似乎正在对大量记录进行未编入索引的搜索,这可能需要很长时间

    第一次尝试这个

    SELECT location.*, A.*
    FROM
    (
        SELECT MAX(startIpNum) AS max_startIpNum
        FROM A 
        WHERE (2463400155 BETWEEN startIpNum AND endIpNum
    ) blocks
    INNER JOIN A
    ON A.startIpNum = blocks.max_startIpNum
    INNER JOIN A_location AS location
    ON A.locId = location.locId
    

    这将依赖于表 A 上 startIpNum 上的索引和表 A_location 上 locid 上的索引。

    第二次试试这个:-

    SELECT location.*, A.*
    FROM A_location AS location
    INNER JOIN
    ( 
        SELECT locId, MAX(startIpNum) AS max_startIpNum
        FROM A 
        WHERE (2463400155 BETWEEN startIpNum AND endIpNum
        GROUP BY locId
    ) blocks
    ON location.locId = blocks.locId
    INNER JOIN A
    ON A.locId = blocks.locId
    AND A.startIpNum = blocks.max_startIpNum
    

    这将依赖于表 A 上覆盖 locid 和 startIpNum 的索引,而表 A_location 上 locid 上的索引可能会有所帮助

    【讨论】:

      【解决方案3】:

      您当前的查询我已更新如下,请在查询下方运行并检查响应时间,我 99.99% 确信它会给您最好的结果

      Step 1: ALTER TABLE A ORDER BY startIpNum DESC;
      
      
      Step 2: SET timestamp=1394367480;
      SELECT location.*, (SELECT * FROM A WHERE (3998482191 BETWEEN startIpNum AND endIpNum) LIMIT 1) AS blocks FROM A_location AS location, WHERE location.locId = blocks.locId;
      
      Step 1: ALTER TABLE A ORDER BY startIpNum DESC;
      
      Step 2: SET timestamp=1394367525;
      SELECT location.*, (SELECT * FROM A WHERE (2463400155 BETWEEN startIpNum AND endIpNum) LIMIT 1) AS blocks FROM A_location AS location WHERE location.locId = blocks.locId;
      

      我刚刚使用 ALTER TABLE A ORDER BY startIpNum DESC;

      从您的查询和表更改中删除 ORDER BY field DESC

      【讨论】:

      • 抱歉,问题出在实际的 BETWEEN 语句中。它没有使用 startIpNum 和 endIpNum 上的两个索引,所以速度非常慢。但是,您通过 startIpNum 列更改表以对其进行排序是正确的,因此它解决了我的问题。非常感谢!
      • @AlexShumilov - MySQL 将(通常)使用一个索引或另一个。因此需要 2 列上的覆盖索引
      【解决方案4】:

      试试这个:

      SELECT location.*, blocks.*
      FROM A_location AS location 
      join  A as blocks on (location.locId = blocks.locId)
      WHERE startIpNum<=3998482191 AND endIpNum>=3998482191
      group by block.locId
      ORDER BY startIpNum DESC;
      

      【讨论】:

        【解决方案5】:

        A 上应该只有一个复合索引:A(startIpNum, endIpNum)。似乎第二个查询对要使用的最佳索引感到困惑。

        以下内容重写查询以使用显式join。这不应该对性能产生影响:

        SELECT *
        FROM (SELECT *
              FROM A
              WHERE 3998482191 BETWEEN startIpNum AND endIpNum
              ORDER BY startIpNum DESC
              LIMIT 1
             ) blocks join
             A_location location
             on location.locId = blocks.locId;
        

        【讨论】:

          猜你喜欢
          • 2011-06-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-04-17
          • 2017-03-05
          • 2016-12-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多