Подтвердить что ты не робот

Запрос MYSQL выполняется очень медленно

Я разработал модуль массовой загрузки пользователя. Есть 2 ситуации, когда я делаю массовую загрузку 20 000 записей, когда база данных имеет нулевые записи. Его занимает около 5 часов. Но когда база данных уже содержит около 30 000 записей, загрузка очень медленная. Загрузка 20 000 записей занимает около 11 часов. Я просто читаю CSV файл с помощью метода fgetcsv.

if (($handle = fopen($filePath, "r")) !== FALSE) {
            while (($peopleData = fgetcsv($handle, 10240, ",")) !== FALSE) {
                if (count($peopleData) == $fieldsCount) {

//inside i check if user already exist (firstName & lastName & DOB)
//if not, i check if email exist. if exist, update the records.
//other wise insert a new record.
}}}

Ниже приведены запросы, которые запускаются. (Я использую структуру Yii)

SELECT * 
FROM `AdvanceBulkInsert` `t` 
WHERE renameSource='24851_bulk_people_2016-02-25_LE CARVALHO 1.zip.csv' 
LIMIT 1

SELECT cf.*, ctyp.typeName, cfv.id as customId, cfv.customFieldId, 
       cfv.relatedId, cfv.fieldValue, cfv.createdAt 
FROM `CustomField` `cf` 
    INNER JOIN CustomType ctyp on ctyp.id = cf.customTypeId 
    LEFT OUTER JOIN CustomValue cfv on cf.id = cfv.customFieldId 
                and relatedId = 0 
    LEFT JOIN CustomFieldSubArea cfsa on cfsa.customFieldId = cf.id 
WHERE ((relatedTable = 'people' and enabled = '1') 
  AND (onCreate = '1')) 
  AND (cfsa.subarea='peoplebulkinsert') 
ORDER BY cf.sortOrder, cf.label

SELECT * 
FROM `User` `t` 
WHERE `t`.`firstName`='Franck' 
  AND `t`.`lastName`='ALLEGAERT ' 
  AND `t`.`dateOfBirth`='1971-07-29' 
  AND (userType NOT IN ("1")) 
LIMIT 1

Если существует обновление пользователя:

UPDATE `User` SET `id`='51394', `address1`='49 GRANDE RUE', 
                  `mobile`='', `name`=NULL, `firstName`='Franck', 
                  `lastName`='ALLEGAERT ', `username`=NULL, 
                  `password`=NULL, `email`=NULL, `gender`=0, 
                  `zip`='60310', `countryCode`='DZ', 
                  `joinedDate`='2016-02-23 10:44:18', 
                  `signUpDate`='0000-00-00 00:00:00', 
                  `supporterDate`='2016-02-25 13:26:37', `userType`=3, 
                  `signup`=0, `isSysUser`=0, `dateOfBirth`='1971-07-29', 
                  `reqruiteCount`=0, `keywords`='70,71,72,73,74,75', 
                  `delStatus`=0, `city`='AMY', `isUnsubEmail`=0, 
                  `isManual`=1, `isSignupConfirmed`=0, `profImage`=NULL, 
                  `totalDonations`=NULL, `isMcContact`=NULL, 
                  `emailStatus`=NULL, `notes`=NULL, 
                  `addressInvalidatedAt`=NULL, 
                  `createdAt`='2016-02-23 10:44:18', 
                  `updatedAt`='2016-02-25 13:26:37', `longLat`=NULL 
WHERE `User`.`id`='51394'

Если пользователь не существует, вставьте новую запись.

Тип настольного компьютера - MYISAM. Только столбец электронной почты имеет индекс.

Как я могу оптимизировать это, чтобы сократить время обработки?

Запрос 2, занял 0.4701 секунд, что означает, что для 30 000 записей потребуется 14103 секунды, что составляет около 235 минут. около 6 часов.

Обновление

CREATE TABLE IF NOT EXISTS `User` (
  `id` bigint(20) NOT NULL,
  `address1` text COLLATE utf8_unicode_ci,
  `mobile` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL,
  `name` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
  `firstName` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `lastName` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `username` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `password` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
  `email` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
  `gender` tinyint(2) NOT NULL DEFAULT '0' COMMENT '1 - female, 2-male, 0 - unknown',
  `zip` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL,
  `countryCode` varchar(3) COLLATE utf8_unicode_ci DEFAULT NULL,
  `joinedDate` datetime DEFAULT NULL,
  `signUpDate` datetime NOT NULL COMMENT 'User signed up date',
  `supporterDate` datetime NOT NULL COMMENT 'Date which user get supporter',
  `userType` tinyint(2) NOT NULL,
  `signup` tinyint(2) NOT NULL DEFAULT '0' COMMENT 'whether user followed signup process 1 - signup, 0 - not signup',
  `isSysUser` tinyint(1) NOT NULL DEFAULT '0' COMMENT '1 - system user, 0 - not a system user',
  `dateOfBirth` date DEFAULT NULL COMMENT 'User date of birth',
  `reqruiteCount` int(11) DEFAULT '0' COMMENT 'User count that he has reqruited',
  `keywords` text COLLATE utf8_unicode_ci COMMENT 'Kewords',
  `delStatus` tinyint(2) NOT NULL DEFAULT '0' COMMENT '0 - active, 1 - deleted',
  `city` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `isUnsubEmail` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0 - ok, 1 - Unsubscribed form email',
  `isManual` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0 - ok, 1 - Manualy add',
  `longLat` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Longitude and Latitude',
  `isSignupConfirmed` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'Whether user has confirmed signup ',
  `profImage` tinytext COLLATE utf8_unicode_ci COMMENT 'Profile image name or URL',
  `totalDonations` float DEFAULT NULL COMMENT 'Total donations made by the user',
  `isMcContact` tinyint(1) DEFAULT NULL COMMENT '1 - Mailchimp contact',
  `emailStatus` tinyint(2) DEFAULT NULL COMMENT '1-bounced, 2-blocked',
  `notes` text COLLATE utf8_unicode_ci,
  `addressInvalidatedAt` datetime DEFAULT NULL,
  `createdAt` datetime NOT NULL,
  `updatedAt` datetime DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE IF NOT EXISTS `AdvanceBulkInsert` (
  `id` int(11) NOT NULL,
  `source` varchar(256) NOT NULL,
  `renameSource` varchar(256) DEFAULT NULL,
  `countryCode` varchar(3) NOT NULL,
  `userType` tinyint(2) NOT NULL,
  `size` varchar(128) NOT NULL,
  `errors` varchar(512) NOT NULL,
  `status` char(1) NOT NULL COMMENT '1:Queued, 2:In Progress, 3:Error, 4:Finished, 5:Cancel',
  `createdAt` datetime NOT NULL,
  `createdBy` int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `CustomField` (
  `id` int(11) NOT NULL,
  `customTypeId` int(11) NOT NULL,
  `fieldName` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `relatedTable` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `defaultValue` text COLLATE utf8_unicode_ci,
  `sortOrder` int(11) NOT NULL DEFAULT '0',
  `enabled` char(1) COLLATE utf8_unicode_ci DEFAULT '1',
  `listItemTag` char(1) COLLATE utf8_unicode_ci DEFAULT NULL,
  `required` char(1) COLLATE utf8_unicode_ci DEFAULT '0',
  `onCreate` char(1) COLLATE utf8_unicode_ci DEFAULT '1',
  `onEdit` char(1) COLLATE utf8_unicode_ci DEFAULT '1',
  `onView` char(1) COLLATE utf8_unicode_ci DEFAULT '1',
  `listValues` text COLLATE utf8_unicode_ci,
  `label` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
  `htmlOptions` text COLLATE utf8_unicode_ci
) ENGINE=MyISAM AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE IF NOT EXISTS `CustomFieldSubArea` (
  `id` int(11) NOT NULL,
  `customFieldId` int(11) NOT NULL,
  `subarea` varchar(256) COLLATE utf8_unicode_ci NOT NULL
) ENGINE=MyISAM AUTO_INCREMENT=43 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE IF NOT EXISTS `CustomValue` (
  `id` int(11) NOT NULL,
  `customFieldId` int(11) NOT NULL,
  `relatedId` int(11) NOT NULL,
  `fieldValue` text COLLATE utf8_unicode_ci,
  `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=MyISAM AUTO_INCREMENT=86866 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Весь код PHP здесь http://pastie.org/10737962

Обновление 2

Объяснить вывод запроса

введите описание изображения здесь

4b9b3361

Ответ 1

Индексы - ваш друг.

UPDATE User ... WHERE id = ... - Отчаянно нужен индекс для ID, возможно PRIMARY KEY.

Аналогично для renameSource.

SELECT * 
FROM `User` `t` 
WHERE `t`.`firstName`='Franck' 
  AND `t`.`lastName`='ALLEGAERT ' 
  AND `t`.`dateOfBirth`='1971-07-29' 
  AND (userType NOT IN ("1")) 
LIMIT 1;

Требуется INDEX(firstName, lastName, dateOfBirth); поля могут быть в любом порядке (в этом случае).

Посмотрите на каждый запрос, чтобы узнать, что ему нужно, затем добавьте в таблицу INDEX. Прочитайте мою Поваренную книгу по созданию индексов.

Ответ 2

Попробуйте эти вещи, чтобы повысить производительность вашего запроса:

  • определить индексирование в структуре базы данных и получить только нужные столбцы.
  • Не используйте * в выбранном запросе.
  • И не помещайте идентификаторы в кавычки типа User.id='51394', а не User.id= 51394.
  • Если вы указываете идентификаторы в кавычках, то ваша индексация не будет работать. Этот подход повышает эффективность запросов на 20% быстрее.
  • Если вы используете ENGINE=MyISAM, вы не сможете определить индексирование между таблицей базы данных, измените механизм базы данных на ENGINE=InnoDB. И создайте некоторые индексирования, такие как внешние ключи, полнотекстовое индексирование.

Ответ 3

Если я понимаю, для всего результата SELECT * FROM AdvanceBulkInsert... вы запустите запрос SELECT cf.*, а для всех SELECT cf.* запустите SELECT * FROM User

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

Я думаю, вы должны объединить весь свой запрос на выбор только в одном большом запросе.

Для этого:

  • заменить SELECT * FROM AdvanceBulkInsert с помощью EXISTS IN (SELECT * FROM AdvanceBulkInsert where ...) или JOIN

  • замените SELECT * FROM User на NOT EXISTS IN(SELECT * from User WHERE )

Затем вы вызываете обновление по всему результату объединенного select.

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

Edit:

Теперь я вижу ваш код:

Некоторое количество свинца:

  • У вас есть индекс для cf.customTypeId, cfv.customFieldId, cfsa.customFieldId, пользователь. dateOfBirth, пользователь. firstName, user.lastName?

  • вам не нужно делать LEFT JOIN CustomFieldSubArea, если у вас есть WHERE, которые используют CustomFieldSubArea, простой JOIN CustomFieldSubArea является enougth.

  • Вы запустите запрос 2 много времени с relatedId = 0, возможно, вы можете сохранить результат в var?

  • если вам не нужны отсортированные данные, удалите "ORDER BY cf.sortOrder, cf.label". Повторите, добавьте индекс на cf.sortOrder, cf.label

Ответ 4

Когда вам нужно выяснить, почему запрос занимает много времени, вам необходимо проверить отдельные части. Как вы показали в вопросе Объяснить выражение может вам очень помочь. Обычно наиболее важными являются:

  • select_type - это всегда должен быть простой запрос/подзапрос. Связанные подзапросы дают много проблем. К счастью, вы не используете никаких
  • возможные ключи. Какие ключи выбраны для поиска по
  • rows - сколько строк кандидатов определяется ключами/кешем и другими методами. Меньшее число лучше
  • Дополнительно - "использование" сообщает вам, как точно найдены строки, это самая полезная информация.

Анализ запросов

Я бы разместил аналитику для первого и третьего запросов, но они оба довольно простые запросы. Вот разбивка запроса, которая дает вам проблемы:

EXPLAIN SELECT cf.*, ctyp.typeName, cfv.id as customId, cfv.customFieldId, 
   cfv.relatedId, cfv.fieldValue, cfv.createdAt 
FROM `CustomField` `cf` 
    INNER JOIN CustomType ctyp on ctyp.id = cf.customTypeId 
    LEFT OUTER JOIN CustomValue cfv on cf.id = cfv.customFieldId 
                and relatedId = 0 
    LEFT JOIN CustomFieldSubArea cfsa on cfsa.customFieldId = cf.id 
WHERE ((relatedTable = 'people' and enabled = '1') 
  AND (onCreate = '1')) 
  AND (cfsa.subarea='peoplebulkinsert') 
ORDER BY cf.sortOrder, cf.label
  • INNER JOIN CustomType ctyp on ctyp.id= cf.customTypeId
  • LEFT OUTER JOIN CustomValue cfv on cf.id= cfv.customFieldId              и relatedId = 0
  • LEFT JOIN CustomFieldSubArea cfsa на cfsa.customFieldId= cf.id
  • WHERE ((relatedTable = 'people' и enabled = '1') AND (onCreate = '1')) И (cfsa.subarea = 'peoplebulkinsert')
  • ORDER BY cf.sortOrder, cf.label

Решение

Позвольте мне пояснить выше список. Полужирный столбцы должны иметь индекс. Соединение таблиц - дорогостоящая операция, которая в противном случае должна проходить через все строки обеих таблиц. Если вы сделаете индекс на соединяемых столбцах, механизм БД найдет гораздо более быстрый и лучший способ сделать это. Это должно быть обычной практикой для любой базы данных

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

Итак, вам нужно добавить указатели на те столбцы

  • CustomType - id
  • CustomField - customTypeId, id, relatedTable, enabled, onCreate, sortOrder, label
  • CustomValue - customFieldId
  • CustomFieldSubArea - customFieldId, подрайон

Чтобы проверить результаты, попробуйте запустить объяснение снова после добавления указателей (и, возможно, нескольких других запросов на выбор/вставку/обновление). Дополнительный столбец должен сказать что-то вроде столбца "Использование индекса" и possible_keys, который должен отображать используемые ключи (даже два или более запросов на соединение).

Боковое примечание: у вас есть некоторые опечатки в коде, вы должны исправить их, если кому-то еще нужно будет работать над вашим кодом: "reqruiteCount" в качестве столбца таблицы и "fileUplaod" в качестве индекса массива в вашем ссылочном коде.

Ответ 5

Для моей работы я должен ежедневно добавлять один CSV с 524 столбцами и 10k записей. Когда я попытаюсь разобрать его и добавить запись с php, это было ужасно.

Итак, я предлагаю вам посмотреть документацию о LOAD DATA LOCAL INFILE

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

$dataload = 'LOAD DATA LOCAL INFILE "'.$filename.'"
                REPLACE
                INTO TABLE '.$this->csvTable.' CHARACTER SET "utf8"
                FIELDS TERMINATED BY "\t"
                IGNORE 1 LINES
            ';

$result = (bool)$this->db->query($dataload);

Где $filename - это локальный путь вашего CSV (вы можете использовать dirname(__FILE__) для его получения)

Эта команда SQL очень быстро (всего 1 или 2 секунды для добавления/обновления всего CSV)

EDIT: прочитайте документ, но, конечно, вам нужно иметь индекс uniq в вашей таблице пользователей для работы "replace". Таким образом, вам не нужно проверять, существует ли пользователь или нет. И вам не нужно разбирать CSV файл с помощью php.

Ответ 6

Кажется, у вас есть вероятность (вероятность?) трех запросов для каждой отдельной записи. Этим 3 запросам потребуются 3 поездки в базу данных (и если вы используете yii для хранения записей в объектах yii, это может замедлить работу еще больше).

Можете ли вы добавить уникальный ключ с именем/фамилией/DOB и по одному на адрес электронной почты?

Если это так, вы можете просто сделать INSERT.... ON DUPLICATE KEY UPDATE. Это уменьшит его до одного запроса для каждой записи, что значительно ускорит процесс.

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

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

Другой вариант - прочитать все в таблице temp, а затем использовать это как источник для присоединения к вашей пользовательской таблице, чтобы делать обновления/вставлять. Это потребует немного усилий с индексами, но массовая загрузка в временную таблицу выполняется быстро, а обновления с полезными индексами будут быстрыми. Использование его в качестве источника для вставок также должно быть быстрым (если исключить уже обновленные записи).

Другая проблема - это ваш следующий запрос, но не знаете, где вы это выполняете. Кажется, что его нужно выполнить только один раз, и в этом случае это может не иметь особого значения. Вы не указали структуру таблицы CustomType, но она присоединена к Customfield, а поле customTypeId не имеет индекса. Следовательно, соединение будет медленным. Точно так же на соединения CustomValue и CustomFieldSubArea, которые соединяются на основе customFieldId, и не имеют индекса в этом поле (надеюсь, что уникальный индекс, как если бы эти поля не были уникальными, вы получите много возвращенных записей - 1 строка для каждой возможной комбинации)

SELECT cf.*, ctyp.typeName, cfv.id as customId, cfv.customFieldId, 
       cfv.relatedId, cfv.fieldValue, cfv.createdAt 
FROM `CustomField` `cf` 
    INNER JOIN CustomType ctyp on ctyp.id = cf.customTypeId 
    LEFT OUTER JOIN CustomValue cfv on cf.id = cfv.customFieldId 
                and relatedId = 0 
    LEFT JOIN CustomFieldSubArea cfsa on cfsa.customFieldId = cf.id 
WHERE ((relatedTable = 'people' and enabled = '1') 
  AND (onCreate = '1')) 
  AND (cfsa.subarea='peoplebulkinsert') 
ORDER BY cf.sortOrder, cf.label

Ответ 7

Посмотрите на это, вы можете попытаться уменьшить запрос и проверить с помощью онлайн-компилятора sql, чтобы проверить период времени, включенный в проект.

Ответ 8

Всегда выполняйте массовый импорт в течение транзакции

        $transaction = Yii::app()->db->beginTransaction();
        $curRow = 0;
        try
        {
            while (($peopleData = fgetcsv($handle, 10240, ",")) !== FALSE) {
            $curRow++;
            //process $peopleData
            //insert row
            //best to use INSERT ... ON DUPLICATE  KEY UPDATE
            // a = 1
            // b = 2;
            if ($curRow % 5000 == 0) {
               $transaction->commit();
               $transaction->beginTransaction();
            }
        }
        catch (Exception $ex)
        {
            $transaction->rollBack();
            $result = $e->getMessage();                    
        }
        //don't forget the remainder.
        $transaction->commit();

Я видел, что процедуры импорта ускорялись на 500%, просто используя эту технику. Я также видел процесс импорта, в котором было 600 запросов (смесь из списка, вставки, обновления и отображения структуры таблицы) для строки каждая. Этот метод ускорил процесс 30%.