以下查询用于执行成员搜索,在此示例中,仅使用姓氏。如果搜索完全匹配的名称,查询将在几秒钟内返回;但如果 :LastName = 'S'
,则查询需要超过 12 秒才能返回。
我怎样才能加快这个查询?如果我可以在一秒钟内用两个查询完成,难道我不应该只用一个查询就可以一样快吗?因为插件和其他方法,我最容易拥有这个是一个查询,因此是我的问题。
Member
表包含我们曾经拥有的所有成员。该表有一些我们没有注册的成员,因此它们只存在于该表中,而不存在于 Registration
或 Registration_History
中。 Registration_History
包含我想要显示的大多数成员的额外信息。 Registration
有大部分和RH相同的信息(RH有一些Reg没有的字段),但有时它有一些RH没有的成员,这就是它加入这里的原因。 编辑:成员(member)在注册中可以有多行。我想填写 Registration_History 中的列,但是,一些遗留成员仅存在于 Registration 中。与其他成员不同,这些遗留成员在注册中只有 1 行,所以我不需要担心注册是如何排序的,只是它只从那里抓取 1 行。
SQL Fiddle with sample database design
MemberID
在所有 3 个表中都有索引。在我放入 SELECT RHSubSelect.rehiId
子查询之前,这个查询几乎花了整整一分钟才返回。
如果我将查询拆分为 2 个查询,则这样做:
SELECT
MemberID
FROM
Member
WHERE
Member.LastName LIKE CONCAT('%', :LastName, '%')
然后将那些 MemberID
放入数组并将该数组传递给 RHSubSelect.MemberID IN ($theArray)
(而不是 Member 子查询),结果来了返回非常快(大约一秒钟)。
完整查询:(完整的 SELECT 语句在 Fiddle 中,SELECT *
为简洁起见)
SELECT
*
FROM
Member
LEFT JOIN
Registration_History FORCE INDEX (PRIMARY)
ON
Registration_History.rehiId = (
SELECT
RHSubSelect.rehiId
FROM
Registration_History AS RHSubSelect
WHERE
RHSubSelect.MemberID IN (
SELECT
Member.MemberID
FROM
Member
WHERE
Member.LastName LIKE CONCAT('%', :LastName, '%')
)
ORDER BY
RHSubSelect.EffectiveDate DESC
LIMIT 0, 1
)
LEFT JOIN
Registration FORCE INDEX(MemberID)
ON
Registration.MemberID = Member.MemberID
WHERE
Member.LastName LIKE CONCAT('%', :LastName, '%')
GROUP BY
Member.MemberID
ORDER BY
Relevance ASC,LastName ASC,FirstName asc
LIMIT 0, 1000
MySQL 解释,在查询中使用 FORCE INDEX()
:
(如果有解释的图片没有显示,它也在这里:http://oi41.tinypic.com/2iw4t8l.jpg)
最佳答案
我的建议是这样的查询:
SELECT *
FROM Member
LEFT JOIN Registration USING (MemberID)
LEFT JOIN Registration_History ON rehiID = (
SELECT rehiID
FROM Registration_History AS RHSubSelect
WHERE RHSubSelect.MemberID = Member.MemberID
ORDER BY EffectiveDate DESC
LIMIT 1
)
WHERE Member.LastName LIKE CONCAT('%', :LastName, '%')
它的工作方式是,您首先从匹配姓氏 的成员 表中进行选择。然后您可以通过简单的 LEFT JOIN
连接到 Registration 表,因为特定成员在该表中最多可以有 1 个条目。最后,您使用子选择LEFT JOIN
Registration_History 表。
子选择查找与当前 MemberID 匹配的最近 EffectiveDate 并返回该记录的 rehiID。 LEFT JOIN
必须与 rehiID 精确匹配。如果该成员的 Registration_History 中没有条目,则不会加入任何内容。
理论上这应该相对较快,因为您只在主查询中执行 LIKE
比较。 Registration 连接应该很快,因为该表是在 MemberID 上建立索引的。但是,我怀疑您需要在 Registration_History 上添加一个索引才能获得最佳性能。
您已经获得了主键 rehID 的索引,这是我们在 rehID 上进行 LEFT JOIN
所需的。但是,子查询需要匹配 WHERE
子句中的 MemberID 以及按 EffectiveDate 排序。为了在那里获得最佳性能,我认为您需要一个额外的索引来组合 MemberID 和 EffectiveDate 列。
请注意,我的示例查询只是为了简单起见的最低要求。您显然需要将 *
替换为您想要返回的所有字段(与您的原始查询相同)。此外,您还需要添加 ORDER BY
和 LIMIT
子句。但是,GROUP BY
不是必需的。
SQL Fiddle 链接:http://sqlfiddle.com/#!2/4a947a/1
上面的 fiddle 显示了完整的查询,除了它有硬编码的姓氏。我修改了您的原始示例数据以包含更多记录并更改了一些值。我还在 Registration_History 表中添加了额外的索引。
针对 LIMIT 进行优化
如果您要再次进行计时运行,我很想知道在使用 Kickstart 建议的修改对 Member< 进行子选择时我的查询如何执行/em> 表,然后加入 Registration 和 Registration_History 表。
SELECT
COALESCE(NULLIF(Registration_History.RegYear, ''), NULLIF(Registration.Year, '')) AS RegYear,
COALESCE(NULLIF(Registration_History.RegNumber, ''), NULLIF(Registration.RegNumber, ''), NULLIF(Member.MemberID, '')) AS RegNumber,
Member.MemberID,
Member.LastName,
Member.FirstName,
Member.Relevance
FROM (
SELECT MemberID, LastName, FirstName,
CASE
WHEN Member.LastNameTrimmed = :LastName THEN 1
WHEN Member.LastNameTrimmed LIKE CONCAT(:LastName, '%') THEN 2
ELSE 3
END AS Relevance
FROM Member
WHERE Member.LastName LIKE CONCAT('%', :LastName, '%')
ORDER BY Relevance ASC,LastName ASC,FirstName ASC
LIMIT 0, 1000
) Member
LEFT JOIN Registration USING (MemberID)
LEFT JOIN Registration_History ON rehiID = (
SELECT rehiID
FROM Registration_History AS RHSubSelect
WHERE RHSubSelect.MemberID = Member.MemberID
ORDER BY EffectiveDate DESC
LIMIT 1
)
当使用 LIMIT 时,这应该比我原来的查询执行得更好,因为它不必为被 LIMIT 排除的记录执行一堆不必要的连接。
关于mysql - 优化子查询,使两个查询成为一个,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17615158/