【问题标题】:SQL query performance question (multiple sub-queries)SQL查询性能问题(多个子查询)
【发布时间】:2010-10-08 07:52:51
【问题描述】:

我有这个问题:

SELECT p.id, r.status, r.title
FROM page AS p
    INNER JOIN page_revision as r ON r.pageId = p.id AND (
        r.id = (SELECT MAX(r2.id) from page_revision as r2 WHERE r2.pageId = r.pageId AND r2.status = 'active')
        OR r.id = (SELECT MAX(r2.id) from page_revision as r2 WHERE r2.pageId = r.pageId)
    )

返回每个页面和每个页面的最新活动修订,除非没有可用的活动修订,在这种情况下它只返回最新修订。

有没有什么方法可以优化它以提高性能或提高一般可读性?我现在没有任何问题,但我担心的是,当它进入生产环境(可能有很多页面)时,它的性能会很差。

另外,我应该注意哪些明显的问题?子查询的使用总是困扰着我,但据我所知,没有它们就无法做到。

注意:
条件在 JOIN 而不是 WHERE 子句中的原因是在其他查询中(使用相同的逻辑)我从“站点”表到“页面”表的左连接,如果不存在页面我仍然希望网站返回。

杰克

编辑:我正在使用 MySQL

【问题讨论】:

  • 表 page_revision 和 page 上有哪些索引?
  • 页面:id,siteId。 page_revision: id, pageId

标签: sql mysql performance join subquery


【解决方案1】:

如果“活动”是按字母顺序排列的第一个,您可以将子查询减少到:

SELECT p.id, r.status, r.title
FROM page AS p
    INNER JOIN page_revision as r ON r.pageId = p.id AND 
        r.id = (SELECT r2.id 
                FROM page_revision as r2 
                WHERE r2.pageId = r.pageId 
                ORDER BY r2.status, r2.id DESC
                LIMIT 1)

否则你可以用

替换 ORDER BY 行
ORDER BY CASE r2.status WHEN 'active' THEN 0 ELSE 1 END, r2.id DESC

这些都来自我对 SQL Server 的假设,您对 MySQL 的了解可能会有所不同。

【讨论】:

  • 啊哈,我曾使用“FIELD(r2.status, 'active')”做过类似的事情,但这更好,谢谢。 :-)
  • 您仍应注意我们正在添加额外的订购。如果你只有“活动”和“非活动”,这应该是一样的,但如果你有更多,事情将首先按状态排序。为避免这种情况,您可能仍想使用 ORDER BY CASE。
  • 值得指出的是,您可以完全在内部联接中完成此操作,而无需对 page_revision 进行第二次查询
【解决方案2】:

也许需要进行一些重构?

如果您在pages 上添加了latest_revision_id 列,您的问题就会消失,希望您的页面编辑器中只添加几行代码。

我知道它没有被规范化,但它会简化(并大大加快)查询,有时你必须为了性能而去规范化。

【讨论】:

    【解决方案3】:

    MS SQL 2005+Oracle

    SELECT p.id, r.status, r.title
    FROM (
      SELECT p.*, r,*,
             ROW_NUMBER() OVER (PARTITION BY p.pageId ORDER BY CASE WHEN p.status = 'active' THEN 0 ELSE 1 END, r.id DESC) AS rn
      FROM page AS p, page_revision r
      WHERE r.id = p.pageId
      ) o
    WHERE rn = 1
    

    MySQL 中,这可能会成为一个问题,因为子查询不能使用INDEX RANGE SCAN,因为外部查询的表达式不被视为常量。

    您需要创建两个索引和一个返回最后一页修订的函数以使用这些索引:

    CREATE INDEX ix_revision_page_status_id ON page_revision (page_id, id, status);
    
    CREATE INDEX ix_revision_page_id (page_id, id);
    
    CREATE FUNCTION `fn_get_last_revision`(input_id INT) RETURNS int(11)
    BEGIN
      DECLARE id INT;
      SELECT r_id
      INTO id
      FROM (
        SELECT r.id
        FROM page_revisions
        FORCE INDEX (ix_revision_page_status_id)
        WHERE page_id = input_id
          AND status = 'active'
        ORDER BY id DESC 
        LIMIT 1
        UNION ALL
        SELECT r.id
        FROM page_revisions
        FORCE INDEX (ix_revision_page_id)
        WHERE page_id = input_id
        ORDER BY id DESC 
        LIMIT 1
      ) o
      LIMIT 1;
      RETURN id;
    END;
    
    SELECT po.id, r.status, r.title
    FROM (
      SELECT p.*, fn_get_last_revision(p.page_id) AS rev_id
      FROM page p
    ) po, page_revision r
    WHERE r.id = po.rev_id;
    

    这将有效地使用索引来获取页面的最新版本。

    P. S. 如果您将使用代码表示状态并使用0 表示活动,则可以去掉第二个索引并简化查询。

    【讨论】:

    • 谢谢,但我使用的是 MySQL,所以我认为这行不通。
    【解决方案4】:

    您的问题是此question 中描述的特殊情况。

    使用标准 ANSI SQL 可以获得的最佳效果似乎是:

    SELECT p.id, r.status, r.title
    FROM page AS p
    INNER JOIN page_revision as r ON r.pageId = p.id 
    AND r.id = (SELECT MAX(r2.id) from page_revision as r2 WHERE r2.pageId = r.pageId)
    

    其他方法可用,但取决于您使用的数据库。我不确定它是否可以对 MySQL 进行很大改进。

    【讨论】:

    • 这似乎对最新的“活动”修订没有任何偏好,就像我最初的查询那样,或者我错过了什么?
    猜你喜欢
    • 2013-02-18
    • 2012-10-29
    • 2019-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多