SQL查询性能问题(多个子查询)

时间:2009-02-18 12:00:09

标签: sql mysql performance join subquery

我有这个问题:

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

4 个答案:

答案 0 :(得分:2)

或许有一些重新分解是有序的吗?

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

我知道它没有规范化,但它会简化(并大大加快)查询,有时你必须对性能进行非规范化。

答案 1 :(得分:2)

如果“active”是按字母顺序排列的第一个,则您可以将子查询缩减为:

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的里程可能会有所不同。

答案 2 :(得分:0)

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作为活动状态,则可以删除第二个索引并简化查询。

答案 3 :(得分:0)

您的问题是此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改进很多。