MySQL загружает ЦП на 100% при включении подзапроса в запрос

Заранее спасибо за помощь.

Мой сервер внезапно перешел на 100% загрузку ЦП. Отключив все сценарии и постепенно восстановив их, я обнаружил, что проблема связана с одним конкретным типом запроса, который используется в двух местах. Запросы:

select * from zzproductdata
where amazonproductid <> '' and amazoncategory1 ='' and disabled = 0
and productid in (select productid from zzdropshipstock where quantity >= 10)

И:

select 'ZZ Product Data With Image' as 'Statistic', count(zzproductdataid) as 
'Number' from zzproductdata
where imagescollecteddate <> '0000-00-00' and zzproductdata.productid in 
(select productid from zzdropshipstock where quantity >= 80)

Оба запроса содержат подзапрос, и оба имеют дело с таблицей под названием zzproductdata.

Проверено пару вещей: 1. Остальные запросы, содержащие подзапросы, продолжают работать. 2. Другие запросы, содержащие zzproductdata, продолжают работать, пока они не содержат подзапрос.

Когда выполняются два вышеуказанных запроса, загрузка ЦП постепенно увеличивается до 100% в течение примерно 90 секунд. Он остается на уровне 100% и, похоже, не восстанавливается - я позволил ему работать так в течение 3 часов, и он не остановил и не завершил запрос.

Таблица zzproductdata постепенно собирала данные и увеличилась до 1 500 000 записей за последние 12 дней. Я отслеживаю количество времени, которое требуется для выполнения каждого запроса в базе данных, и эти два запроса постепенно становятся длиннее, но не чрезмерно - с 2,5 секунд до 5,6 секунд.

Внезапно они просто начали переходить на 100% ЦП без предупреждения. В списке процессов mysqladmin в терминале я вижу две записи: одна показывает процесс в состоянии «Спящий», а вторая - статус «Отправка данных».

Я подозреваю, что запросы только что достигли какого-то ограничения кеша sql, но я не могу понять, какой предел кеша используется mysql для использования подзапросов. Я попытался изменить некоторые настройки в mysql, но, похоже, ни один из них не дал эффекта. Какие-либо предложения?

РЕДАКТИРОВАТЬ

CREATE TABLE `zzproductdata` (
 `zzproductdataid` int(11) NOT NULL AUTO_INCREMENT,
 `zzproductdataname` text NOT NULL,
 `disabled` int(11) NOT NULL,
 `datecreated` date NOT NULL,
 `masteronly` int(11) NOT NULL,
 `productid` int(11) NOT NULL,
 `isbn` text NOT NULL,
 `ean` text NOT NULL,
 `publishername` text NOT NULL,
 `imagethumbnail` text NOT NULL,
 `imagefull` text NOT NULL,
 `amazonproductid` text NOT NULL,
 `productdatasource` text NOT NULL,
 `datelastupdatedamazon` date NOT NULL,
 `datelastupdatedgoogle` date NOT NULL,
 `googleproductid` text NOT NULL,
 `publicationdate` date NOT NULL,
 `binding` text NOT NULL,
 `imagescollecteddate` date NOT NULL,
 `amazoncategory1` text NOT NULL,
 `amazoncategory2` text NOT NULL,
 `amazoncategory3` text NOT NULL,
 `datelastcheckedamazoncategory` date NOT NULL,
 `datelastupdatedopenlibrary` date NOT NULL,
 PRIMARY KEY (`zzproductdataid`)
)
ENGINE=InnoDB
AUTO_INCREMENT=1674296
DEFAULT CHARSET=utf

CREATE TABLE `zzdropshipstock` (
 `zzdropshipstockid` int(11) NOT NULL AUTO_INCREMENT,
 `zzdropshipstockname` text NOT NULL,
 `disabled` int(11) NOT NULL,
 `datecreated` date NOT NULL,
 `masteronly` int(11) NOT NULL,
 `zzdropshipsupplierid` int(11) NOT NULL,
 `isbn` varchar(13) NOT NULL,
 `quantity` double NOT NULL,
 `suppliercode` text NOT NULL,
 `supplierprice` double NOT NULL,
 `standardshipcost` double NOT NULL,
 `weightgram` double NOT NULL,
 `rrp` double NOT NULL,
 `productid` int(11) NOT NULL,
 `initialimportdate` date NOT NULL,
 `lastupdateddate` date NOT NULL,
 `changed` int(11) NOT NULL,
 `lastcheckedproductiddate` date NOT NULL,
 `lastcheckedproductinamazondate` date NOT NULL,
 `lastcheckedrawstockdata` date NOT NULL,
 `lastcheckedproductstockitemlive` date NOT NULL,
 `changedquantity` int(11) NOT NULL,
 PRIMARY KEY (`zzdropshipstockid`),
 KEY `isbn` (`isbn`)
)
ENGINE=InnoDB
AUTO_INCREMENT=7037817
DEFAULT CHARSET=latin1

Используя EXPLAIN по первому запросу - я получаю следующее: MySQL загружает ЦП на 100% при включении подзапроса в запрос

Вот что я вижу в Терминале: MySQL загружает ЦП на 100% при включении подзапроса в запрос

Я пробовал добавить DISTINCT, но это не помогло. Это замедлило рост использования ЦП, поэтому потребовалось почти 200 секунд, чтобы достичь 100% использования ЦП, но запрос не был выполнен.

Первая часть запроса возвращает 1 300 000 записей за 0,23 секунды. Подзапрос возвращает 118 000 записей за 2,3 секунды. Это когда вы запускаете две части по отдельности. Объединение этих двух запросов в один в настоящее время должно вернуть около 15 000 записей.

Есть ли у вас какие-либо индексы в таблицах zzproductdata и zzdropshipstock? Было бы неплохо, если бы вы предоставили вывод SHOW CREATE TABLE zzproductdata; вместе с поясняющей информацией для этих запросов.

fifonik 14.12.2018 01:25

Вы пробовали добавлять DISTINCT в свои подзапросы? Вы также можете после добавления DISTINCT переместить подзапрос в FROM и INNER JOIN в него. MySQL плохо справляется с условиями OR (по сравнению со многими другими условиями), а IN в основном является OR (хотя я понимаю, что в последних версиях для IN были сделаны оптимизации).

Uueerdo 14.12.2018 01:56

Не думаю, что DISTINCT поможет. Для первого запроса, я думаю, проблема в том, что слишком много результатов («Отправка данных»). Я думаю, что "amazonproductid <> '' и amazoncategory1 = '' and disabled = 0" в WHERE верно для большинства записей (1,5 млн), и, вероятно, в подзапросе довольно много продуктов. Вот почему я попросил ОБЪЯСНИТЬ.

fifonik 14.12.2018 03:48
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
3
3
458
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я не совсем уверен, как MySQL обрабатывает IN по сравнению с EXISTS, но в целом вам следует предпочесть EXISTS, а не IN, поскольку предложение EXISTS возвращает true при первом совпадении, тогда как IN, возможно, придется создать полный набор, прежде чем он определит, есть ли совпадение - это могло привести к высокому использованию памяти. Я не уверен, что в случае с MySQL. Я знаю, что эта проблема существовала в MSSQL 2005 и, возможно, в 2008 году, и я не удивлюсь, если такая же проблема существует даже в современных версиях MySQL, учитывая, что она не так совершенна, как некоторые СУБД корпоративного уровня.

Попробуйте этот код, чтобы увидеть, улучшит ли он ситуацию:

select *
from zzproductdata as pd
where amazonproductid <> ''
and amazoncategory1 =''
and disabled = 0
and exists (
    select *
    from zzdropshipstock as dss
    where quantity >= 10
    and pd.productid = dss.productid
)

Коррелированный подзапрос против 1,5 миллионов строк может усугубить ситуацию. Хотя IN должен создать полный набор, он делает это только один раз; EXISTS должен выполняться для каждой соответствующей внешней строки и стоит тем больше, чем меньше вероятность найти существующую запись (удаление обходится дороже, чем включение).

Uueerdo 14.12.2018 02:02

Спасибо за предложение. Это не улучшило положение вещей. Он просто заставил ЦП перейти на 100% за 12 секунд вместо 90 секунд. Я не знаю, помогает ли это увеличение скорости достижения максимальной загрузки ЦП выявить проблему.

Andy1234 14.12.2018 03:51

@Uueerdo "СУЩЕСТВУЮЩИЕ должны выполнить" --- не так ли? SQL не подразумевает, что правильные оптимизаторы запросов превратят его в INNER JOIN.

zerkms 14.12.2018 04:12

Проблема не в СУЩЕСТВОВАНИИ, а в коррелированном характере запроса внутри него.

Uueerdo 14.12.2018 18:36

Я согласен с тем, что коррелированные подзапросы сложнее по своей природе. Вот почему оптимизатор довольно часто преобразует его в соединение как IN (подзапрос). К сожалению, без знания версии sql, существующих данных и статистики, доступных для сервера, это только предположение.

fifonik 14.12.2018 23:07
Ответ принят как подходящий

Для начала вы можете попробовать добавить эти индексы:

  1. ALTER TABLE zzdropshipstock ADD INDEX IX_zzdropshipstock_key (quantity, productid);

  2. ALTER TABLE zzproductdata ADD INDEX IX_zzproductdata_productid (productid);

Затем выполните ANALYZE TABLE zzdropshipstock, ANALYZE TABLE zzproductdata и покажите новый план EXPLAIN.

В каждой таблице нет индекса между productid, потому что это заставляет другие запросы сильно замедляться в других местах. Даже без индекса между двумя полями productid он работал нормально через 5,6 секунды, а затем просто перестал работать при следующем запуске. Я уверен, что вы правы в том, что добавление этих индексов устранит конкретную проблему в этом запросе, и мне, возможно, придется это сделать, а затем изменить способ работы других скриптов по отношению к этим таблицам. Но очень странно, что он работал нормально за 5,6 секунды, а затем просто останавливался и максимально загружал процессор.

Andy1234 14.12.2018 04:24

Индексы не должны сильно замедлять выполнение других запросов (вставка / обновление может выполняться медленнее). Сейчас SQL-серверу приходится обрабатывать 1,5 млн строк в каждой таблице. Это слишком много. У него заканчивается оперативная память, и начался свопинг. Без индексов проблему не решить. Когда раньше в таблицах было меньше данных, они умещались в памяти, поэтому выполнялись довольно быстро. Но больше не будет.

fifonik 14.12.2018 04:26

Да, к сожалению, в обеих таблицах в настоящий момент каждый час происходит 1000 вставок, а также 10000 изменений каждый час. Добавление этих индексов значительно замедляет эти вставки. Если я правильно понимаю, у меня есть варианты: либо увеличить память на сервере, либо добавить индексы / принять дополнительное время, необходимое для вставок.

Andy1234 14.12.2018 04:32

Тогда вам нужно найти другой способ. Нельзя ожидать, что SQL-сервер «объединит» две 1,5 млн таблиц без индексов в разумные сроки. Вы можете добавить больше ОЗУ, но вы столкнетесь с той же проблемой еще через 2 недели. Я не верю, что предлагаемые индексы сильно повлияют на 1000 вставок в час и 10000 обновлений в час. Просто попробуйте добавить хотя бы второй предложенный индекс. Также можно попробовать оптимизировать вставки: dev.mysql.com/doc/refman/5.7/en/…

fifonik 14.12.2018 04:39

Спасибо, попробую. Спасибо за помощь, дружище.

Andy1234 14.12.2018 04:43

Другие вопросы по теме