Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/mysql/58.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
为什么向这个MySQL查询添加一个特定的where子句是一个性能瓶颈?_Mysql_Sql_Performance_Optimization - Fatal编程技术网

为什么向这个MySQL查询添加一个特定的where子句是一个性能瓶颈?

为什么向这个MySQL查询添加一个特定的where子句是一个性能瓶颈?,mysql,sql,performance,optimization,Mysql,Sql,Performance,Optimization,抱歉,篇幅太长,我想给出一个完整的描述!我需要显示一个报告,其中显示了来自另一个表的id的一些信息,以及当某人在x天内从某个国家/地区更改国家/地区时的信息。请注意,对于一个id,我可以在表中多次使用相同的国家/地区条目(因为信息会以固定的间隔多次查询,但在此期间它们可能没有移动),也可以使用不同的国家/地区条目(因为它们更改了国家/地区) 数据的快速解释: 我有下表: CREATE TABLE IF NOT EXISTS `country` ( `id` mediumint(8) unsign

抱歉,篇幅太长,我想给出一个完整的描述!我需要显示一个报告,其中显示了来自另一个表的id的一些信息,以及当某人在x天内从某个国家/地区更改国家/地区时的信息。请注意,对于一个id,我可以在表中多次使用相同的国家/地区条目(因为信息会以固定的间隔多次查询,但在此期间它们可能没有移动),也可以使用不同的国家/地区条目(因为它们更改了国家/地区)

数据的快速解释: 我有下表:

CREATE TABLE IF NOT EXISTS `country` (
`id` mediumint(8) unsigned NOT NULL,
`timestamp` datetime NOT NULL,
`country` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`,`timestamp`),
KEY `country` (`country`),
KEY `timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
入口是这样的:

41352   2012-03-26 15:46:01     Jamaica
41352   2012-03-05 22:49:41     Jamaican Applicant
41352   2012-02-26 15:46:01     Jamaica
41352   2012-02-16 12:11:19     Jamaica
41352   2012-02-05 23:00:30     Jamaican Applicant
该表目前总共有约214590行,但一旦用实际数据替换测试数据,将有数百万行

我想要的是关于从y时间以来离开x国家的每个人的一些信息。以下是我希望它的输出方式,假设它是在上面的数据上运行的:

id  name    last    country     TIMESTAMP   o_timestamp
41352 Sweet Mercy   Jamaica     2012-03-26 15:46:01     2012-03-05 22:49:41
41352 Sweet Mercy   Jamaica     2012-02-16 12:11:19     2012-02-05 23:00:30
其中o_timestamp比某个日期(比如说100)新,国家是他们迁往的地方,他们来自的旧国家(未显示)是我传入查询的任何国家(牙买加申请人基于上述数据)

我开发了以下查询以满足需求,并使用某个id进行测试:

SELECT a.id,
       c.name,
       c.last,
       a.country,
       a.timestamp,
       b.timestamp AS o_timestamp
FROM   country a
       INNER JOIN user_info c
         ON ( a.id = c.id )
       LEFT JOIN country AS b
         ON ( a.id = b.id
              AND a.timestamp != b.timestamp
              AND a.country != b.country )
WHERE  b.timestamp = (SELECT c.timestamp
                      FROM   country c
                      WHERE  a.id = c.id
                             AND a.timestamp > c.timestamp
                      ORDER  BY c.timestamp DESC
                      LIMIT  1) 
       AND a.id = 965
我在7分钟内完成了这项工作(共7次,查询耗时0.0050秒)

一项解释揭示了以下情况:

id  select_type     table   type    possible_keys   key     key_len     ref     rows    filtered    Extra
1   PRIMARY     c   const   PRIMARY     PRIMARY     3   const   1   100.00  
1   PRIMARY     a   ref     PRIMARY     PRIMARY     3   const   16  100.00  
1   PRIMARY     b   eq_ref  PRIMARY,timestamp   PRIMARY     11  const,func  1   100.00  Using where
2   DEPENDENT SUBQUERY  c   index   PRIMARY,timestamp   timestamp   8   NULL    1   700.00  Using where; Using index
所以我觉得我做得很好,就这样突然出现了:

SELECT a.id,
       c.name,
       c.last,
       a.country,
       a.timestamp,
       b.timestamp AS o_timestamp
FROM   country a
       INNER JOIN user_info c
         ON ( a.id = c.id )
       LEFT JOIN country AS b
         ON ( a.id = b.id
              AND a.timestamp != b.timestamp
              AND a.country != b.country )
WHERE  b.timestamp = (SELECT c.timestamp
                      FROM   country c
                      WHERE  a.id = c.id
                             AND a.timestamp > c.timestamp
                      ORDER  BY c.timestamp DESC
                      LIMIT  1) 
       AND b.country = "whatever" AND timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
这个查询花了惊人的6分54秒在一个拥有200项记录但从未完成的国家完成(在进行了下午、晚上和晚上的查询之后)

对于一个拥有9000条数据库记录的国家来说,回家总共需要大约8个小时)。从实际数据来看,一个国家可以轻松上万次。10万美元是合理的

因此,我确实解释了扩展,并得出以下结论:

id  select_type     table   type    possible_keys   key     key_len     ref     rows    filtered    Extra
1   PRIMARY     <derived2>  ALL     NULL    NULL    NULL    NULL    3003    100.00  
1   PRIMARY     c   eq_ref  PRIMARY     PRIMARY     3   b.id    1   100.00  
1   PRIMARY     a   ref     PRIMARY     PRIMARY     3   b.id    7   100.00  Using where
3   DEPENDENT SUBQUERY  c   index   PRIMARY,timestamp   timestamp   8   NULL    1   700.00  Using where; Using index
2   DERIVED     country     range   country,timestamp   country     195     NULL    474     100.00  Using where; Using index
id选择类型表类型可能的键参考行过滤额外
1主所有空值3003 100.00
1初级c均衡参考初级3 b.id 1100.00
1主a参考主3 b.id 7 100.00使用where
3依赖子查询c索引主,时间戳时间戳8 NULL 1700.00使用where;使用索引
2衍生国家范围国家,时间戳国家195 NULL 474 100.00使用where;使用索引
因此,它看起来更大,但并非毫无道理

[删除了空间的配置变量,如果需要,请告诉我以及性能信息,因为这可能是一个查询问题。]


如果我遗漏了什么,请告诉我。

问题不在于增加标准;它正在扔下一个造成伤害的东西。在原始查询中,您有:

AND a.id = 965
这意味着查询执行不需要读取整个
a
country
)表。在第二个性能失效查询中,将该条件更改为:

AND b.country = "whatever"
AND timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
您不再对
a
有真正的限制性标准,因此工作速度要慢得多

当人们意识到
b
是对
country
的另一种引用时,事情就变得更加复杂了。然而,从
a
上的条件到
b
(其中
b
位于外部连接的外侧)的变化并非微不足道;处理查询条件需要更长的时间


这是否意味着因为我不是在寻找一个特定的身份证,我就不走运了

对于给定的查询结构,答案似乎是“是”,但我们可以说,给定的查询结构可能是次优的

您的“处理一个ID时足够快”查询是:

SELECT a.id,
       c.name,
       c.last,
       a.country,
       a.timestamp,
       b.timestamp AS o_timestamp
FROM   country a
       INNER JOIN user_info c
         ON ( a.id = c.id )
       LEFT JOIN country AS b
         ON ( a.id = b.id
              AND a.timestamp != b.timestamp
              AND a.country != b.country )
WHERE  b.timestamp = (SELECT c.timestamp
                      FROM   country c
                      WHERE  a.id = c.id
                             AND a.timestamp > c.timestamp
                      ORDER  BY c.timestamp DESC
                      LIMIT  1) 
       AND a.id = 965
我不完全理解这个查询以及它试图做什么。您需要知道,外部联接比内部联接更昂贵,外部联接表上的条件类似于

b.timestamp = (...correlated sub-query...)
都贵得要命。一个问题是,在
b
列(包括
timestamp
)中可能有一个NULL,但是子查询在该列上浪费了时间,因为除非值非NULL,否则条件不会得到满足,所以我们最后想知道“为什么要进行外部连接”

添加修订后的条件时,您应该收到“不明确的列名”错误,因为该时间戳可能来自
a
c
。另外,
b.country=“where”
条件是另一个只有在
b
值不为空时才有意义的条件,因此,外部连接也是可疑的

据我所知,
country
表包含关于谁进入哪个国家以及何时进入的记录。另外,FWIW,我相当肯定与
user\u info
表的连接是一个可以忽略的性能问题;问题完全在于对
国家/地区
表的三次引用


从一些澄清判断,您可以增量地构建查询,可能是这样的

  • 查找同一
    id
    的每一对国家/地区记录,其中记录在时间顺序上相邻,且一对记录中的较早者适用于给定的国家(“牙买加申请人”),较新者适用于不同的国家/地区

    其中最简单的部分是:

    SELECT a.id, a.country, a.timestamp, b.country, b.timestamp
      FROM country AS a
      JOIN country AS b
        ON a.id = b.id
       AND b.timestamp > a.timestamp
       AND a.country = 'Jamaica Applicant'
       AND b.country != a.country
    
    这可以完成大部分工作,但不能确保条目的邻接性。为此,我们必须坚持在
    country
    表中,在两个时间戳之间(但不包括)没有相同
    id
    的记录,
    a.timestamp
    b.timestamp
    。这是一个不存在的额外条件:

    SELECT a.id,
           a.country   AS o_country,
           a.timestamp AS o_timestamp,
           b.country   AS n_country,
           b.timestamp AS n_timestamp
      FROM country AS a
      JOIN country AS b
        ON a.id = b.id
       AND b.timestamp > a.timestamp
       AND a.country = 'Jamaica Applicant'
       AND b.country != a.country
     WHERE NOT EXISTS
           (SELECT *
              FROM country AS c
             WHERE c.timestamp > a.timestamp
               AND c.timestamp < b.timestamp
               AND c.id = a.id
           )
    
  • 我不打算保证性能会更好(甚至不保证它在语法上是正确的;它还没有通过SQL DBMS)。但是,我认为用于获取相邻日期的复杂查询结构比原始代码更整洁,性能可能更好。请特别注意,它不使用任何外部联接(显式)排序
    SELECT e.id, u.name, u.last, e.o_country, e.o_timestamp, e.n_country, e_n_timestamp
      FROM (SELECT a.id,
                   a.country   AS o_country,
                   a.timestamp AS o_timestamp,
                   b.country   AS n_country,
                   b.timestamp AS n_timestamp
              FROM country AS a
              JOIN country AS b
                ON a.id = b.id
               AND b.timestamp > a.timestamp
               AND a.country = 'Jamaica Applicant'
               AND b.country != a.country
             WHERE NOT EXISTS
                   (SELECT *
                      FROM country AS c
                     WHERE c.timestamp > a.timestamp
                       AND c.timestamp < b.timestamp
                       AND c.id = a.id
                   )
           ) AS e
      JOIN user_info AS u ON e.id = u.id
     WHERE e.o_timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY);
    
    SELECT ui.*, c1.*, MAX(c2.timestamp)
    FROM country c1
    INNER JOIN user_info ui
        ON c1.id = ui.id
    INNER JOIN country c2
        ON c1.id = c2.id
        AND c1.timestamp > c2.timestamp
        AND c1.country <> c2.country
    WHERE c2.timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
    AND c2.country = 'somewhere'
    GROUP BY c1.id
    
    SELECT ui.*, c1.*, c2.timestamp
    FROM country c1
    INNER JOIN user_info ui
        ON c1.id = ui.id
    INNER JOIN country c2
        ON c1.id = c2.id
        AND c1.timestamp > c2.timestamp
        AND c1.country <> c2.country
    
    LEFT JOIN country c3
        ON c1.id = c3.id
        AND c1.timetsamp > c3.timestamp
        AND c2.timestamp < c2.timetsamp
    
    WHERE c2.timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
    AND c2.country = 'somewhere'
    AND c3.id IS NULL