mysql - 优化子查询,使两个查询成为一个

标签 mysql query-optimization

以下查询用于执行成员搜索,在此示例中,仅使用姓氏。如果搜索完全匹配的名称,查询将在几秒钟内返回;但如果 :LastName = 'S',则查询需要超过 12 秒才能返回。

我怎样才能加快这个查询?如果我可以在一秒钟内用两个查询完成,难道我不应该只用一个查询就可以一样快吗?因为插件和其他方法,我最容易拥有这个是一个查询,因此是我的问题。

Member 表包含我们曾经拥有的所有成员。该表有一些我们没有注册的成员,因此它们只存在于该表中,而不存在于 RegistrationRegistration_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(): "Mysql Explain"

(如果有解释的图片没有显示,它也在这里: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 并返回该记录的 rehiIDLEFT JOIN 必须与 rehiID 精确匹配。如果该成员的 Registration_History 中没有条目,则不会加入任何内容。

理论上这应该相对较快,因为您只在主查询中执行 LIKE 比较。 Registration 连接应该很快,因为该表是在 MemberID 上建立索引的。但是,我怀疑您需要在 Registration_History 上添加一个索引才能获得最佳性能。

您已经获得了主键 rehID 的索引,这是我们在 rehID 上进行 LEFT JOIN 所需的。但是,子查询需要匹配 WHERE 子句中的 MemberID 以及按 EffectiveDate 排序。为了在那里获得最佳性能,我认为您需要一个额外的索引来组合 MemberIDEffectiveDate 列。

请注意,我的示例查询只是为了简单起见的最低要求。您显然需要将 * 替换为您想要返回的所有字段(与您的原始查询相同)。此外,您还需要添加 ORDER BYLIMIT 子句。但是,GROUP BY 不是必需的。

SQL Fiddle 链接:http://sqlfiddle.com/#!2/4a947a/1

上面的 fiddle 显示了完整的查询,除了它有硬编码的姓氏。我修改了您的原始示例数据以包含更多记录并更改了一些值。我还在 Registration_History 表中添加了额外的索引。

针对 LIMIT 进行优化

如果您要再次进行计时运行,我很想知道在使用 Kickstart 建议的修改对 Member< 进行子选择时我的查询如何执行/em> 表,然后加入 RegistrationRegistration_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/

相关文章:

mysql - 将 MySQL 数据/查询集转换为等效的 Cassandra 表示

php - 使用php将字符串转换为逗号分隔的数组

php - 连接两个查询以形成结果集

mysql - 我应该用什么代替 IN?

mysql - MySQL 是否可以使用复合索引,其中一个字段是 WHERE 字段 > 0?

php - 喜欢一些MySql Optimization techniques for Bulk data table

MySQL ORDER BY 非常慢 - 即使有索引

php - 我应该将我的网站升级到 PHP MySQLi 还是 PDO?

php - MySQL 和 PHP,mysqli_num_rows 始终返回 0

sql - 一次只获取 N 行 (MySQL)