2016-04-09 3 views
2

У меня очень сложный запрос, который использует некоторые подзапросы в операторе CASE.Как предотвратить зависимые подзапросы в CASE КОГДА x (подзапрос)

Для этого вопроса полный запрос не нужен и просто не позволит людям быстро попасть в проблему.

Так что этот пост использует псевдокод для работы. Если захочу, я мог бы отправить запрос, но это монстр и не нужен для этого вопроса.

Что я хочу, это кэшируемые подзапросы в операторе CASE.

SELECT * FROM posts posts 
INNER JOIN posts_shared_to shared_to 
     ON shared_to.post_id = posts.post_id 
INNER JOIN channels.channels 
     ON channels.channel_id = shared_to.channel_id 
WHERE posts.parent_id IS NULL 
AND MATCH (post.text) AGAINST (:keyword IN BOOLEAN MODE) 
AND CASE(
    WHEN channel.read_access IS NULL THEN 1 
    WHEN channel.read_access = 1 THEN 
    (
     SELECT count(*) FROM channel_users 
     WHERE user_id = XXX AND channel_id = channels.channel_id 
    ) 
    WHEN shared_to.read_type = 2 THEN 
    (
     /* another subquery with a join */ 
     /* check if user is in friendlist of post_author */ 
    ) 
    ELSE 0 
    END; 
) 
GROUP BY post.post_id 
ORDER BY post.post_id 
DESC LIMIT n,n 

Как статут над этим является просто упрощенным псевдокодом.

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

Любое решение, которое помогает ускорить этот запрос, приветствуется.

EDITED ЧАСТЬ: Теперь истинный запрос выглядит следующим образом:

SELECT a.id, a.title, a.message AS post_text, a.type, a.date, a.author AS uid, 
b.a_name as name, b.avatar, 
shared_to.to_circle AS circle_id, shared_to.root_circle, 
c.circle_name, c.read_access, c.owner_uid, c.profile, 
MATCH(a.title,a.message) AGAINST (:keyword IN BOOLEAN MODE) AS score 

FROM posts a 

/** get userdetails for post_author **/ 
JOIN authors b ON b.id = a.author 

/** get circles posts was shared to **/ 
JOIN posts_shared_to shared_to ON shared_to.post_id = a.id AND shared_to.deleted IS NULL 

/** 
* get circle_details note: at the moment shared_to can contain NULL and 1 too and doesnt need to be a circle_id 
* if to_circle IS NULL post was shared public 
* if to_circle = 1 post was shared to private circles 
* since we use md5 keys as circle ids this can be a string insetad of (int) ... ugly.. 
* 
**/ 
LEFT JOIN circles c ON c.circle_id = shared_to.to_circle 
    /*AND c.circle_name IS NOT NULL */ 
    AND (c.profile IS NULL OR c.profile = 6 OR c.profile = 1) 
    AND c.deleted IS NULL 

LEFT JOIN (
    /** if post is within a channel that requires membership we use this to check if requesting user is member **/ 
    SELECT COUNT(*) users_count, user_id, circle_id FROM circles_users 
    GROUP BY user_id, circle_id 
    ) counts ON counts.circle_id = shared_to.to_circle 
      AND counts.user_id = :me 

LEFT JOIN (
    /** if post is shared private we check if requesting users exists within post authors private circles **/ 
    SELECT count(*) in_circles_count, ci.owner_uid AS circle_owner, cu1.user_id AS user_me 
    FROM circles ci 
    INNER JOIN circles_users cu1 ON cu1.circle_id = ci.circle_id 
           AND cu1.deleted IS NULL 
    WHERE ci.profile IS NULL AND ci.deleted IS NULL 
    GROUP BY user_me, circle_owner 
) users_in_circles ON users_in_circles.user_me = :me 
        AND users_in_circles.circle_owner = a.id 

/** make sure post is a topic **/ 
WHERE a.parent_id IS NULL AND a.deleted IS NULL 

/** search title and post body **/ 
AND MATCH (a.title,a.message) AGAINST (:keyword IN BOOLEAN MODE) 

AND (
    /** own circle **/ 
    c.owner_uid = :me 
    /** site member read_access (this query is for members, for guests we use a different query) **/ 
    OR (c.read_access = 1 OR c.read_access = "1") 
    /** public read_access **/ 
    OR (shared_to.to_circle IS NULL OR (c.read_access IS NULL AND c.owner_uid IS NOT NULL)) 
    /** channel/circle member read_access**/ 
    OR (c.read_access = 3 OR c.read_access = "3" AND counts.users_count > 0) 
    /** for users within post creators private circles **/ 
    OR ( 
    ( 
    /** use shared_to to determine if post is private **/ 
    shared_to.to_circle = "1" OR shared_to.to_circle = 1 
    /** use circle settings to determine global privacy **/ 
    OR (c.owner_uid IS NOT NULL AND c.read_access = 2 OR c.read_access = "2") 
    ) AND users_in_circles.circle_owner = a.author AND users_in_circles.user_me = :me 
    ) 
) 

GROUP BY a.id ORDER BY a.id DESC LIMIT n,n 

Вопрос: ли это на самом деле лучше? Если я посмотрю, сколько строк могут содержать производные таблицы, я не уверен в этом.

А может быть, кто-то может помочь мне изменения запроса, как упомянуто @ Олли-Джонс:

SELECT stuff, stuff, stuff 
    FROM (
     SELECT post.post_id 
      FROM your whole query 
      ORDER BY post_id DESC 
      LIMIT n,n 
     ) ids 
    JOIN whatever ON whatever.post_id = ids.post_id 
    JOIN whatelse ON whatelse 

SRY, если этот звук slazy, но я на самом деле не mysqlguy и у меня головные боли в течение многих лет только от строительства этот запрос. : D

ответ

2

Лучший способ устранить ваш зависимый подзапрос - это реорганизовать его так, чтобы он представлял собой виртуальную таблицу (независимый подзапрос), затем СОЕДИНЯЙТЕСЬ или ЛЕВАЙТЕСЬ С ПРИСОЕДИНЯЙТЕСЬ к остальной части ваших таблиц.

В вашем случае, у вас есть

 SELECT count(*) FROM channel_users 
     WHERE user_id = XXX AND channel_id = channels.channel_id 

Таким образом, независимый-подзапрос литье это

    SELECT COUNT(*) users_count, 
          user_id, channel_id 
        FROM channel_users 
        GROUP BY user_id, channel_id 

Вы видите, как это виртуальная таблица содержит одну строку для каждой отдельной комбинации user_id и channel_id значений? Каждая строка имеет значение users_count. Затем вы можете присоединиться к этому в остальной части вашего запроса, например. (Обратите внимание на то, что внутреннее соединение === JOIN в MySQL, так что я использовал РЕГИСТРИРУЙТЕСЬ, чтобы сократить его немного.)

SELECT * FROM posts posts 
    JOIN posts_shared_to shared_to ON shared_to.post_id = posts.post_id 
    JOIN channels.channels ON channels.channel_id = shared_to.channel_id 
    LEFT JOIN (
        SELECT COUNT(*) users_count, 
          user_id, channel_id 
        FROM channel_users 
        GROUP BY user_id, channel_id 
     ) counts ON counts.channel_id = shared_to.channel_id 
       AND counts.user_id = channels.user_id 
    LEFT JOIN ( /* your other refactored subquery */ 
      ) friendcounts ON whatever 
WHERE posts.parent_id IS NULL 
    AND channels.user_id = XXX 
    AND MATCH (post.text) AGAINST (:keyword IN BOOLEAN MODE) 
    AND (   channel.read_access IS NULL 
       OR (channel.read_access = 1 AND counts.users_count > 0) 
       OR (shared_to.read_type = AND friendcount.users_count > 0) 
     ) 
GROUP BY post.post_id 
ORDER BY post.post_id DESC 
LIMIT n,n 

для MySQL запросов планировщик, как правило, достаточно умен, чтобы генерировать соответствующее подмножество каждого независимого подзапроса.

Pro tip:SELECT lots of columns ... ORDER BY something LIMIT n обычно считается расточительным противником. Он убивает производительность, потому что он сортирует целую кучу столбцов данных, а затем отбрасывает большую часть результата.

Pro tip:SELECT * in JOIN запрос также расточительный.Вам намного лучше, если вы дадите список столбцов, которые вам действительно нужны в вашем результирующем наборе.

Таким образом, вы можете реорганизовать запрос снова сделать

SELECT stuff, stuff, stuff 
     FROM (
      SELECT post.post_id 
       FROM your whole query 
       ORDER BY post_id DESC 
       LIMIT n,n 
      ) ids 
     JOIN whatever ON whatever.post_id = ids.post_id 
     JOIN whatelse ON whatelse. 

Идея заключается в том, чтобы только сортировать по post_id значений, а затем использовать ограниченное подмножество, чтобы вытащить остальные данные вам нужно.

+1

Хотел бы я утверждать, что запросы в моем ответе были отладки и готовы к использованию. Но я не такой уж большой лжец. –

+1

Улыбка. Выглядит гениально. Благодарю. Дает мне достаточно новых идей для работы. – user2429266

+0

Хорошо. Я глубоко вздохнул и работал через ваш ввод. (Трудно создать исходный запрос таким образом. Было сложно построить его вообще.) Является ли независимый подзапрос действительно более эффективным, если они работают на больших таблицах? – user2429266

Смежные вопросы