我的SQL查询包含两部分。第一个很简单:
SELECT * FROM `clients` WHERE id IN (...)
这个将返回有关具有特定ID的客户的所有信息
第二个查询要大得多,它应该为之前的查询准备ID列表(为了更好的可读性,我省略了几个UNION,你可以在最后看到完整的查询):
SELECT client_id
FROM `contact_persons`
WHERE id IN (
SELECT owner_id
FROM `contacts`
WHERE contact_info LIKE '%keyword%' AND company_or_person = 'person'
)
UNION
SELECT owner_id
FROM `contacts`
WHERE contact_info LIKE '%keyword%' AND company_or_person = 'company'
UNION
SELECT id
FROM `clients`
WHERE client_name LIKE '%keyword%'
每个查询都需要不到0.5秒的时间来单独执行。但是,当我将第二个查询放入第一个查询时,它使两个查询完成时间超过一分钟,此时CPU负载跳跃到100%。
SELECT * ...
更改为SELECT id ...
(尽管这会使整个查询无效)。结果:没有改变看起来如果我单独运行查询的这些部分并将ID列表存储在php变量中它会正常工作,但即使对于我作为初学者来说也是如此错误。
SELECT * FROM `clients` WHERE deleted = 0 AND id IN (
SELECT client_id as found
FROM `contact_persons`
WHERE id IN (
SELECT owner_id
FROM `contacts`
WHERE contact_info LIKE '%keyword%' AND company_or_person = 'person'
)
UNION
SELECT owner_id as found
FROM `contacts`
WHERE contact_info LIKE '%keyword%' AND company_or_person = 'company'
UNION
SELECT id as found
FROM `clients`
WHERE client_name LIKE '%keyword%'
UNION
SELECT client_id as found
FROM `cargo`
WHERE cargo_name LIKE '%keyword%'
UNION
SELECT page_id as found
FROM `comments`
WHERE message LIKE '%keyword%' AND page_type = 'client' AND deleted = 0
UNION
SELECT client_id as found
FROM `contact_persons`
WHERE person_name LIKE '%keyword%')
我的数据库非常小(160 Kb),特别是表clients
只有160行和5列。我尝试了所有我想出的但仍然无法解决问题。
我刚刚运行了这个最小的查询,我用UNION
切断了每个部分。结果:几乎相同的时间 - 57秒执行
SELECT SQL_NO_CACHE id FROM `clients` WHERE id IN (
SELECT client_id
FROM `contact_persons`
WHERE id IN (
SELECT owner_id
FROM `contacts`
WHERE contact_info LIKE '%keyword%'
)
)
正如所建议的那样,我试图用IN
替换INNER JOIN
中的一个,这就是诀窍,约60秒现在转向~0.4s
所以而不是
SELECT .. WHERE .. IN (SELECT .. WHERE .. IN (SELECT ..))
我写的就像
SELECT .. AS t1 INNER JOIN (SELECT .. WHERE .. IN (SELECT ..)) AS t2 WHERE ti.id = t2.id
对于人类来说,这是完全相同的事情,但看起来它不适用于MySQL服务器。
如果有人好奇,我会在此处发布EXPLAIN
输出作为最终查询:
答案 0 :(得分:3)
您的查询可能过于复杂,查询优化器会出现错误的优化。正如@huhushow建议的那样,针对您的查询运行EXPLAIN
可能会显示此信息。
您可以尝试几种方法。
更简单的方法是单独运行查询,并以编程方式累积所有ID,然后使用重复数据删除的ID列表构建最后一个查询。这对你来说是“错误的”。实际上,随着更多ID的检索,这种方法可能会变得尴尬。
类似的解决方案是使用INSERT IGNORE
和found
上的唯一索引将查询结果累积到临时表中,以确保重复数据删除。然后,您将在clients表和临时表之间运行JOIN。
一种完全不同的方法是为SQL查询优化器提供更多内存和时间限制,以鼓励它找到更快的查询计划。这里的困难在于干预mysqld的参数(您可能没有被授权这样做)以及稍后更改查询可能导致优化器再次失败的事实;换句话说,这个解决方案不一定稳定。
另一种方法是将外部SELECT扩散到UNION中,从而将UNION带出来;即,而不是做
SELECT a.* WHERE id IN ( select1 UNION select2 UNION select3 )
由于生成id
的位置与使用位置之间的“分离度”太多而无法轻易优化,您可以先做
SELECT a.* WHERE id IN ( select1 )
UNION
SELECT a.* WHERE id IN ( select2 )
UNION
SELECT a.* WHERE id IN ( select3 )
然后,反过来,您将合并IN将其转换为JOIN。例如第一组:
SELECT * FROM `clients` WHERE deleted = 0 AND id IN (
SELECT client_id as found
FROM `contact_persons`
WHERE id IN (
SELECT owner_id
FROM `contacts`
WHERE contact_info LIKE '%keyword%' AND company_or_person
= 'person' )
将成为第一个:
SELECT * FROM `clients` WHERE deleted = 0 AND id IN (
SELECT cp.client_id as found
FROM `contact_persons` AS cp
JOIN `contacts` AS c ON (cp.id = c.owner_id)
WHERE c.contact_info LIKE '%keyword%' AND c.company_or_person
= 'person' )
)
然后最后是一个在优化器上更容易的表单:
SELECT [DISTINCT] clients.* FROM `clients`
JOIN `contact_persons` AS cp ON (clients.id = cp.client_id)
JOIN `contacts` AS c ON (cp.id = c.owner_id)
WHERE clients.deleted = 0
AND c.contact_info LIKE '%keyword%'
AND c.company_or_person = 'person'
作为最后一个阶段,您将添加索引:例如,在上面的案例中,您基于owner_id
({= 1}}从contacts
选择company_or_person
并且{ {1}}。你可以因此
contact_info
这也允许完整地执行所有子查询,并查看是否有其中一个特别减慢了事情。
答案 1 :(得分:0)
首先检查您的查询的解释查询计划。像这样
EXPLAIN SELECT * FROM `clients` WHERE deleted = 0 AND id IN (
SELECT client_id as found
FROM `contact_persons`
WHERE id IN (
SELECT owner_id
FROM `contacts`
WHERE contact_info LIKE '%keyword%' AND company_or_person = 'person'
)
UNION
SELECT owner_id as found
FROM `contacts`
WHERE contact_info LIKE '%keyword%' AND company_or_person = 'company'
UNION
SELECT id as found
FROM `clients`
WHERE client_name LIKE '%keyword%'
UNION
SELECT client_id as found
FROM `cargo`
WHERE cargo_name LIKE '%keyword%'
UNION
SELECT page_id as found
FROM `comments`
WHERE message LIKE '%keyword%' AND page_type = 'client' AND deleted = 0
UNION
SELECT client_id as found
FROM `contact_persons`
WHERE person_name LIKE '%keyword%')
这可以找出查询的哪个部分很慢。 mysql official manual对于理解解释结果非常有帮助。