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