Вопрос производительности SQL-запроса (несколько подзапросов) - PullRequest
0 голосов
/ 18 февраля 2009

У меня есть этот запрос:

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, заключается в том, что в других запросах (где используется та же логика) я ВЗАИМОДЕЙСТВУЮ из таблицы «site» в таблицу «page», и если страниц не существует, я все еще хочу, чтобы сайт вернулся.

Jack

Редактировать: Я использую MySQL

Ответы [ 4 ]

2 голосов
/ 18 февраля 2009

Если «активный» является первым в алфавитном порядке, вы можете уменьшить подзапросы до:

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 голосов
/ 18 февраля 2009

Может, немного ре-факторинга?

Если вы добавите столбец latest_revision_id в pages, ваша проблема исчезнет, ​​возможно, только в несколько строчек добавится ваш редактор страниц.

Я знаю, что это не нормализовано, но это упростит (и значительно ускорит) запрос, и иногда вам придется денормализовать для повышения производительности.

0 голосов
/ 18 февраля 2009

Ваша проблема является частным случаем того, что описано в этом вопросе .

Лучшее, что вы можете получить при использовании стандартного 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.

0 голосов
/ 18 февраля 2009

В 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 для активных, вы можете избавиться от второго индекса и упростить запрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...