PHP; MySQL Запрос JOIN для больших наборов данных замедляется по мере обновления условий WHERE - PullRequest
0 голосов
/ 12 февраля 2020

Так что это может быть немного глупо, но альтернатива, которую я использовал, хуже. Я пытаюсь написать лист Excel, используя данные из моей базы данных и PHP инструмент под названием Box / Spout . Дело в том, что Box / Spout читает строки по одной за раз, и они не извлекаются через индекс (например, строки [10], строки [42], строки [156])

Мне нужно получить данные из база данных в порядке выхода строк. У меня есть база данных со списком клиентов, которая пришла через Import, и я должен записать их в таблицу Excel. У них есть телефонные номера, электронные письма и адрес. Извините за путаницу ...: / Итак, я скомпилировал этот довольно сложный запрос:

SELECT
      `Import`.`UniqueID`,
      `Import`.`RowNum`,
      `People`.`PeopleID`,
      `People`.`First`,
      `People`.`Last`,
      GROUP_CONCAT(
        DISTINCT CONCAT_WS(',', `PhonesTable`.`Phone`, `PhonesTable`.`Type`)
          ORDER BY `PhonesTable`.`PhoneID` DESC
          SEPARATOR ';'
      ) AS `Phones`,
      GROUP_CONCAT(
        DISTINCT CONCAT_WS(',', `EmailsTable`.`Email`)
          ORDER BY `EmailsTable`.`EmailID` DESC
          SEPARATOR ';'
      ) AS `Emails`,
      `Properties`.`Address1`,
      `Properties`.`city`,
      `Properties`.`state`,
      `Properties`.`PostalCode5`,
      ...(17 more `People` Columns)...,
      FROM `T_Import` AS `Import`
      LEFT JOIN `T_CustomerStorageJoin` AS `CustomerJoin` 
        ON `Import`.`UniqueID` = `CustomerJoin`.`ImportID`
      LEFT JOIN `T_People` AS `People` 
        ON `CustomerJoin`.`PersID`=`People`.`PeopleID`
      LEFT JOIN `T_JoinPeopleIDPhoneID` AS `PeIDPhID` 
        ON `People`.`PeopleID` = `PeIDPhID`.`PeopleID`
      LEFT JOIN `T_Phone` AS `PhonesTable` 
        ON `PeIDPhID`.`PhoneID`=`PhonesTable`.`PhoneID`
      LEFT JOIN `T_JoinPeopleIDEmailID` AS `PeIDEmID` 
        ON `People`.`PeopleID` = `PeIDEmID`.`PeopleID`
      LEFT JOIN `T_Email` AS `EmailsTable` 
        ON `PeIDEmID`.`EmailID`=`EmailsTable`.`EmailID`
      LEFT JOIN `T_JoinPeopleIDPropertyID` AS `PeIDPrID` 
        ON `People`.`PeopleID` = `PeIDPrID`.`PeopleID` 
        AND `PeIDPrID`.`PropertyCP`='CurrentImported'
      LEFT JOIN `T_Property` AS `Properties` 
        ON `PeIDPrID`.`PropertyID`=`Properties`.`PropertyID`
      WHERE `Import`.`CustomerCollectionID`=$ccID
        AND `RowNum` >= $rnOffset 
        AND `RowNum` < $rnLimit 
      GROUP BY `RowNum`;

Итак, у меня есть индексы для каждого сегмента ON и сегмента WHERE. Когда значение RowNumber примерно равно 0-> 2500, запрос выполняется отлично и выполняется в течение пары секунд. Но похоже, что время выполнения запроса экспоненциально умножается при увеличении RowNumber.

У меня есть EXPLAIN здесь: и в pastebin (https://pastebin.com/PksYB4n2)

id  select_type table         partitions  type    possible_keys                     key                   key_len   ref                                          rows    filtered    Extra
1   SIMPLE      Import        NULL        ref     CustomerCollectionID,RowNumIndex  CustomerCollectionID  4         const                                        48108   8.74        Using index condition; Using where; Using filesort;
1   SIMPLE      CustomerJoin  NULL        ref     ImportID                          ImportID              4         MyDatabase.Import.UniqueID                       1   100         NULL
1   SIMPLE      People        NULL        eq_ref  PRIMARY,PeopleID                  PRIMARY               4         MyDatabase.CustomerJoin.PersID                   1   100         NULL
1   SIMPLE      PeIDPhID      NULL        ref     PeopleID                          PeopleID              5         MyDatabase.People.PeopleID                       8   100         NULL
1   SIMPLE      PhonesTable   NULL        eq_ref  PRIMARY,PhoneID,PhoneID_2         PRIMARY               4         MyDatabase.PeIDPhID.PhoneID                      1   100         NULL
1   SIMPLE      PeIDEmID      NULL        ref     PeopleID                          PeopleID              5         MyDatabase.People.PeopleID                       5   100         NULL
1   SIMPLE      EmailsTable   NULL        eq_ref  PRIMARY,EmailID,DupeDeleteSelect  PRIMARY               4         MyDatabase.PeIDEmID.EmailID                      1   100         NULL
1   SIMPLE      PeIDPrID      NULL        ref     PeopleMSCP,PeopleID,PropertyCP    PeopleMSCP            5         MyDatabase.People.PeopleID                       4   100         Using where
1   SIMPLE      Properties    NULL        eq_ref  PRIMARY,PropertyID                PRIMARY               4         MyDatabase.PeIDPrID.PropertyID                   1   100         NULL

Прошу прощения, если форматирование абсолютно ужасное. Я не уверен, как выглядит хорошее форматирование, поэтому я, возможно, немного перепутал его с ошибками, плюс запутались вкладки.

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

Я пытался использовать LIMIT 2000, 1000, например, но я знаю, что это менее эффективен, чем использование индексированного столбца. Поэтому я переключился на RowNumber. Я чувствую, что это было хорошее решение, но кажется, что MySQL все еще циклически повторяет каждую строку перед переменной смещением, что побеждает цель моего индекса ... Я думаю? Я не уверен. Я также в основном разделил этот конкретный запрос примерно на 10 отдельных запросов и запускал их один за другим для каждой строки файла Excel. Это занимает много времени ... слишком долго. Это быстро, но, очевидно, у меня проблема.

Любая помощь будет принята с благодарностью, и спасибо заранее. Еще раз прошу прощения за отсутствие организации почты.

Ответы [ 2 ]

1 голос
/ 16 февраля 2020

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

Я называю это синдромом "взрыва-взрыва". Запрос выполняет JOIN, получая набор строк, тем самым генерируя несколько строк, и помещает несколько строк в промежуточную таблицу. Затем GROUP BY возвращается обратно вниз к исходному набору строк.

Позвольте мне сосредоточиться на части запроса, которая может быть переформулирована для обеспечения повышения производительности:

SELECT ...
      GROUP_CONCAT(
        DISTINCT CONCAT_WS(',', `EmailsTable`.`Email`)
          ORDER BY `EmailsTable`.`EmailID` DESC
          SEPARATOR ';'
      ) AS `Emails`,
      ...
    FROM ...
      LEFT JOIN `T_Email` AS `EmailsTable` 
        ON `PeIDEmID`.`EmailID`=`EmailsTable`.`EmailID`
      ...
    GROUP BY `RowNum`;

Вместо этого переместите таблицу и функцию агрегирования в подзапрос

SELECT ...
      ( SELECT GROUP_CONCAT(
                DISTINCT CONCAT_WS(',', `Email`)
                    ORDER BY `EmailID` DESC
                    SEPARATOR ';' )
            FROM T_Email
            WHERE `PeIDEmID`.`EmailID` = `EmailID`
      ) AS `Emails`,
      ...
   FROM ...
      -- and Remove:  LEFT JOIN `T_Email` ON ...
      ...
      -- and possibly Remove:  GROUP BY ...;

То же самое для PhonesTable.

(Неясно, можно ли удалить GROUP BY; для других вещей он может понадобиться .)

1 голос
/ 13 февраля 2020
  • Порядок столбцов в индексе имеет значение. Порядок пунктов в WHERE не имеет значения (обычно).
  • INDEX(a), INDEX(b) равен , а не так же, как «составной» INDEX(a,b). Я специально сделал составные индексы там, где они казались полезными.
  • INDEX(a,b) и INDEX(b,a) не являются взаимозаменяемыми, если только a и b не протестированы с =. (Плюс несколько исключений.)
  • Индекс «покрытия» - это индекс, в котором все столбцы для одной таблицы находятся в одном индексе. Это иногда обеспечивает дополнительное повышение производительности. Некоторые из моих рекомендуемых индексов - «покрытие». Это означает, что требуется доступ только к index BTree, но не к data BTree; это где он набирает некоторую скорость.
  • В EXPLAIN SELECT ... индекс «покрытия» обозначается как «Использование индекса» (что не совпадает с «Использование условия индекса»). (Ваше Объяснение в настоящее время не показывает индексы покрытия.)
  • Индекс «не должен» иметь более 5 столбцов. (Это не жесткое и быстрое правило.) В индексе T5 были столбцы f5 для покрытия; было непрактично делать индекс покрытия для T2.

Когда JOINing, порядок таблиц не имеет значения; Оптимизатор может свободно перетасовывать их. Однако применяются эти «правила»:

  • A LEFT JOIN может принудительно упорядочить таблицы. (Я думаю, что в этом случае.) (Я упорядочил столбцы на основе того, что, по моему мнению, оптимизатор хочет; может быть, есть некоторая гибкость.)
  • Предложение WHERE , обычно определяет с какой таблицы «начинать». (Вы тестируете только на T1, поэтому, очевидно, он начнется с T1.
  • Ссылка на "следующую таблицу" (через NLJ - Nested L oop Join) определяется множеством вещей. (В вашем случае это довольно очевидно - именно столбцы ON).

Подробнее об индексации: http://mysql.rjweb.org/doc.php/index_cookbook_mysql

Пересмотренный запрос

1. Import:  (CustomerCollectionID,  -- '=' comes first
             RowNum,                -- 'range'
             UniqueID)              -- 'covering'
    Import shows up in WHERE, so is first in Explain; Also due to LEFTs
Properties:  (PropertyID)   -- is that the PK?
PeIDPrID:  (PropertyCP, PeopleID, PropertyID)
3. People:  (PeopleID)
    I assume that is the `PRIMARY KEY`?  (Too many for "covering")
    (Since `People` leads to 3 other table; I won't number the rest.)
EmailsTable:  (EmailID, Email)
PeIDEmID:  (PeopleID,    -- JOIN from People
            EmailID)     -- covering
PhonesTable:  (PhoneID, Type, Phone)
PeIDPhID:  (PeopleID, PhoneID)
2. CustomerJoin:  (ImportID,   -- coming from `Import` (see ON...)
                   PersID)     -- covering

После добавления я ожидаю, что большинство строк EXPLAIN скажут Using index.

Отсутствие по крайней мере, составной индекс для Import является основной проблемой, приводящей к вашей жалобе на производительность.

Bad GROUP BY

Когда есть GROUP BY, который не включает все неагрегированные столбцы, которые не зависят напрямую от группы по столбцу (столбцам), вы получаете случайные значения для дополнений. Я вижу из EXPLAIN ("Rows"), что несколько таблиц , вероятно, иметь несколько строк. Вы действительно должны думать о мусоре, генерируемом этим запросом.

Любопытно, что телефоны и электронные письма поступают в GROUP_CONCAT(), тем самым избегая вышеуказанной проблемы, но «Rows» составляет всего 1.

(Читайте о ONLY_FULL_GROUP_BY; это могло бы объяснить проблему лучше.)

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