Left Joins - это то, что я хочу, но они очень медленные? - PullRequest
7 голосов
/ 10 марта 2009

Обзор:

У меня есть три таблицы 1) подписчики, биографии и рубашки, и мне нужно найти подписчиков без биографии или рубашки

таблицы выложены как

подписчики

| season_id |  user_id |

био

| bio_id | user_id |

размеры рубашки

| bio_id | shirtsize |

И мне нужно найти всех пользователей, у которых нет биографии или размера рубашки (если нет биографии, то нет размера рубашки через отношение) для какого-либо конкретного сезона.

Первоначально я написал запрос вроде:

SELECT *
   FROM subscribers s 
   LEFT JOIN bio b ON b.user_id = subscribers.user_id 
   LEFT JOIN shirtsizes ON shirtsize.bio_id = bio.bio_id 
WHERE s.season_id = 185181 AND (bio.bio_id IS NULL OR shirtsize.size IS NULL);

но сейчас требуется 10 секунд.

Мне интересно, как я могу реструктурировать запрос (или, возможно, проблему), чтобы он разумно преформировался.

Вот объяснение mysql: (ogu = подписчики, b = био, tn = shirtshize)

| id | select_type | table | type  | possible_keys | key     | key_len | ref         | rows   | Extra       |   
+----+-------------+-------+-------+---------------+---------+---------+-------------+--------+-------------+    
|  1 | SIMPLE      | ogu   | ref   | PRIMARY       | PRIMARY | 4       | const       |    133 | Using where |
|  1 | SIMPLE      | b     | index | NULL          | PRIMARY | 8       | NULL        | 187644 | Using index |
|  1 | SIMPLE      | tn    | ref   | nid           | nid     | 4       | waka2.b.nid |      1 | Using where | 

Вышесказанное довольно санировано, вот реальная информация:

mysql> DESCRIBE subscribers
+-----------+---------+------+-----+---------+-------+
| Field     | Type    | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+-------+
| subscribers  | int(11) | NO   | PRI |         |       | 
| uid       | int(11) | NO   | PRI |         |       | 


mysql> DESCRIBE bio;
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| bio_id   | int(10) unsigned | NO   | PRI | 0       |       | 
| uid   | int(10) unsigned | NO   | PRI | 0       |       | 


mysql> DESCRIBE shirtsize;
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| bio_id   | int(10) unsigned | NO   | PRI | 0       |       | 
| shirtsize   | int(10) unsigned | NO   | PRI | 0       |       | 

и реальный запрос выглядит так:

SELECT ogu.nid, ogu.is_active, ogu.uid, b.nid AS bio_node, tn.nid AS size
                  FROM og_uid ogu
                  LEFT JOIN bio b ON b.uid = ogu.uid
                  LEFT JOIN term_node tn ON tn.nid = b.nid
                  WHERE ogu.nid = 185033 AND ogu.is_admin = 0
                  AND (b.nid IS NULL OR tn.tid IS NULL)

nid - это season_id или bio_id (с типом); term_node будет размером с рубашку

Ответы [ 9 ]

14 голосов
/ 10 марта 2009

Запрос должен быть в порядке. Я бы провел его через анализатор запросов и уточнил индексы в таблицах.

12 голосов
/ 10 марта 2009

Объединения - это одна из самых дорогих операций, которые вы можете выполнять с SQL-запросом. Хотя он и должен несколько оптимизировать ваш запрос, возможно, попытайтесь реструктурировать его. Прежде всего, я бы вместо SELECT * обязательно указывал, какие столбцы вам нужны, из каких отношений. Это немного ускорит процесс.

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

SELECT s.user_id
   FROM subscribers s 
   LEFT JOIN bio b ON b.user_id = subscribers.user_id 
   LEFT JOIN shirtsizes ON shirtsize.bio_id = bio.bio_id 
WHERE s.season_id = 185181 AND (bio.bio_id IS NULL OR shirtsize.size IS NULL);

Это позволит базе данных SQL самостоятельно более эффективно реструктурировать ваш запрос.

4 голосов
/ 10 марта 2009

Очевидно, что я не проверял это, но, похоже, вам нужно выбрать любого подписчика, для которого нет подходящей биографии или соединение биосов и рубашек не удается. Я хотел бы рассмотреть возможность использования НЕ СУЩЕСТВУЕТ для этого условия. Возможно, вам понадобятся индексы для bio.user_id и shirtsizes.bio_id.

select *
from subscribers
where s.season_id = 185181
      and not exists (select *
                      from bio join shirtsizes on bio.bio_id = shirtsizes.bio_id
                      where bio.user_id = subscribers.user_id)

EDIT

Исходя из вашего обновления, вы можете создать отдельные ключи для каждого столбца вместо / в дополнение к наличию составных первичных ключей. Возможно, что объединения не в состоянии оптимально использовать составные первичные индексы, и индекс самих столбцов объединения может ускорить процесс.

1 голос
/ 11 марта 2009

Ваш запрос, как он написан сейчас, оценивает все bio и term_node, если они существуют, а затем отфильтровывает их.

Но то, что вы хотите, это просто найти og_uid, которые не имеют term_node (не имеющие bio также подразумевают отсутствие term_node)

Так что вы просто хотите прекратить оценку bio и term_node, как только найдете первый существующий term_node:

SELECT  *
FROM    (
        SELECT  ogu.nid, ogu.is_active, ogu.uid,
                (
                SELECT  1
                FROM    bio b, term_node tn
                WHERE   b.uid = ogu.uid
                        AND tn.nid = b.nid
                LIMIT   1
                ) AS ex
        FROM    og_uid ogu
        WHERE   ogu.nid = 185033
                AND ogu.is_admin = 0
        ) ogu1
WHERE   ex IS NULL

Это даст оценку не более одного bio и не более одного term_node для каждого og_uid вместо оценки всех существующих тысяч и их фильтрации.

Должно работать намного быстрее.

1 голос
/ 10 марта 2009

Быстрее ли будет сделать разницу между списком подписчиков на соответствующий сезон и списком подписчиков на сезон с биосами и размерами рубашки?

SELECT *
   FROM Subscribers
   WHERE season_id = 185181
     AND user_id NOT IN
         (SELECT DISTINCT s.user_id
             FROM subscribers s
             JOIN bios b ON s.user_id = b.user_id
             JOIN shirtsizes z ON b.bio_id = z.bio_id
             WHERE s.season_id = 185181
         )

Это позволяет избежать внешних объединений, которые не такие быстрые, как внутренние, и поэтому могут быть быстрее. С другой стороны, это может быть создание двух больших списков с очень небольшими различиями между ними. Не ясно, улучшит ли DISTINCT в подзапросе или повредит производительности. Это подразумевает операцию сортировки (дорогую), но прокладывает путь для объединения слиянием, если оптимизатор MySQL поддерживает такие вещи.

Возможны и другие обозначения, например, MINUS или DIFFERENCE.

1 голос
/ 10 марта 2009

Является ли bio_id первичным ключом BIOS? Действительно ли возможна строка биоса с b.user_id = subscribers.user_id, но с b.bio_id NULL?

Есть ли строки размера рубашки с shirtsize.bio_id NULL? У этих строк когда-либо есть размер рубашки. Размер не НЕДЕЙСТВИТЕЛЕН?

1 голос
/ 10 марта 2009

Если вы определите то, что вы ищете, а не SELECT *, это может немного ускорить его ... также ИЛИ не самый быстрый запрос, если вы можете переписать его без ИЛИ, это быстрее.

Кроме того ... Вы могли бы попробовать объединения вместо левых соединений?

SELECT s.user_id
   FROM subscribers s 
   LEFT JOIN bio b ON b.user_id = s.user_id 
   LEFT JOIN shirtsizes ON shirtsize.bio_id = bio.bio_id 
WHERE s.season_id = 185181 AND (bio.bio_id IS NULL OR shirtsize.size IS NULL);

будет что-то вроде:

(SELECT s.user_id FROM subscribers s WHERE s.season_id = 185181)
UNION
(SELECT b.user_id, b.bio_id FROM bio b WHERE bio.bio_id IS NULL)
UNION
(SELECT shirtsizes.bio_id FROM shirtsizes WHERE shirtsizes.size is NULL)

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

Я бы сделал:

SELECT *
FROM subscribers s, bio b, shirtsizes sh
WHERE s.season_id = 185181
AND shirtsize.bio_id = bio.bio_id 
AND b.user_id = s.user_id 
AND (bio.bio_id IS NULL 
     OR 
     shirtsize.size IS NULL);
0 голосов
/ 11 марта 2009

Я полагаю, что ваша "большая таблица" - это подписчики, и этот season_id, вероятно, не является ни избирательным, ни индексированным (в любом случае индексировать его довольно бессмысленно, если он не избирательный), что означает, что в любом случае вам придется полностью сканировать подписчиков , Расставаясь, я бы присоединился (с внутренним объединением) к двум другим таблицам - обратите внимание, что если в файле shirt_size нет bio_id, то для вашего запроса это точно так же, как если бы не было bio. Первый бит:

select uid
from bio
     inner join shirtsizes
             on shirtsizes.bio_id = bio.bio_id

В этот момент вы хотите проверить, проиндексированы ли размеры рубашки в bio_id. Теперь вы можете оставить внешний присоединиться к этому запросу для подписчиков:

select *
from subscribers s
     left outer join (select uid
                      from bio
                      inner join shirtsizes
                              on shirtsizes.bio_id = bio.bio_id) x
                  on x.uid = s.uid
where s.season_id = 185181
  and x.uid is null

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

0 голосов
/ 10 марта 2009
select * from subscribers where user_id not in (
  select user_id from bio where bio_id not in (
    select bio_id from shirt_sizes
  )
) and season_id=185181
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...