Оператор PDO занимает в 400 раз больше времени, чем MySQL CLI

Я запускаю подготовленный оператор PDO для выбора из таблицы примерно из 6 тыс. Строк. Этот конкретный запрос возвращает все строки из-за оператора WHERE, который имеет ~ 5k pId. Таблица также имеет индекс по столбцу pId.

SELECT * FROM table_a WHERE pId in (?, ? ,? ....)

Для выполнения этого запроса на php требуется 4,5 секунды, а при выполнении в интерфейсе командной строки MySQL - 0,01 секунды. Оператор EXPLAIN для PHP и MySQL одинаков, оба НЕТ используют индекс на pId. Я думаю, это связано с тем, что MySQL знает, что возвращает всю таблицу и не нуждается в индексе.

Я знаю, что с подготовленными операторами есть некоторые накладные расходы, но я запускаю очень похожий запрос (другое имя таблицы) в другом месте, и он не занимает столько времени (~ 0,9 секунды). Любые идеи?

Версия PHP: 5.5

Версия MySql: 5.6

pIds, используемый в предложении in, был получен из другого запроса? Если да, то можно переписать с помощью exists

Felippe Duarte 16.05.2018 18:29

PDO должен установить соединение для выполнения операций. Установите для пула значение true и попробуйте выполнить этот оператор несколько раз.

Lemjur 16.05.2018 18:30

Это быстрее с EMULATE_PREPARES?

Paul Spiegel 16.05.2018 18:33

Вы используете подготовленные операторы в интерфейсе командной строки?

bassxzero 16.05.2018 18:36

Я сомневаюсь, что входит в 4,5 секунды. Это измеряется только у execute. Включает ли это время для звонков bindValue? Звонок prepare? Включает ли это время для вызовов bindResult и время для выборки и обработки строк? Использует ли PDO подготовку на стороне сервера или эмулирует подготовку на стороне клиента? Для сравнения, сколько времени, когда значения включаются в текст SQL без вызовов bindValue? Кроме того, каково время выполнения CLI, когда кеш запроса не задействован SELECT SQL_NO_CACHE * FROM?

spencer7593 16.05.2018 18:47

@bassxzero Я не знаю, я не знал, что могу использовать подготовленные операторы в CLI, но даже никогда не смотрел. Я знаю, что выполнение команды SQL со значениями, уже содержащимися в операторе «where», будет быстрее, но опять же в 400 раз быстрее кажется безумием. Также я запускаю идентичный запрос только к другой таблице, и это занимает четверть времени в PHP.

shreddish 16.05.2018 18:59

@shreddish ~ Подготовка 6k идентификаторов - нетривиальная задача, и я готов поспорить, что именно поэтому ваш запрос такой медленный. Однако вы не узнаете наверняка, пока не протестируете его. Прямо сейчас кажется, что вы сравниваете яблоки с апельсинами. dev.mysql.com/doc/refman/8.0/en/…

bassxzero 16.05.2018 19:02

@ spencer7593 Я попробую получить более детальное профилирование PDO. Прямо сейчас я вызываю функцию-оболочку, чтобы все мои операторы PDO были сгруппированы вместе. Что касается подготовки к эмуляции, для него установлено значение false.

shreddish 16.05.2018 19:05

@bassxzero имеет смысл, что подготовка этого оператора будет иметь некоторые накладные расходы, но почему бы мне не увидеть эти накладные расходы в идентичном подготовленном запросе только в другой таблице?

shreddish 16.05.2018 19:06

@shreddish Это снова, вероятно, проблема яблок с апельсинами. Обе таблицы имеют одинаковое количество строк, запрашиваете ли вы обе таблицы по индексу, обе таблицы разделены одинаково и т. д.?

bassxzero 16.05.2018 19:08

@bassxzero да, другая таблица проиндексирована по pId и имеет около 5,5 тыс. строк и разделена одинаково

shreddish 16.05.2018 19:13

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

bassxzero 16.05.2018 19:15

@ spencer7593, вы что-то поняли, я могу объединить эти 4,5 секунды со временем выполнения запроса. Кажется, что PDO fetchAll занимает ~ 90% времени для этого запроса, а также для остальных моих запросов. Почему fetchAll будет намного длиннее в этом запросе по сравнению с «идентичным» запросом и с очень похожей длиной результата?

shreddish 16.05.2018 19:17

@bassxzero, похоже, ты тоже кое-что понял, ха-ха, мой подход к созданию этих подготовленных операторов был несовершенным с точки зрения отдела эффективности. Вместо того, чтобы создавать ~ 6k значений в операторе «where», я изменил его на одно значение и развернул свой массив pIds для подготовки. Это ЗНАЧИТЕЛЬНО сократило время, спасибо!

shreddish 16.05.2018 19:53

@shreddish "свернул мой массив pIds" - это приведет к чему-то вроде WHERE pid IN ('3,6,8,...'), который затем будет преобразован в INT как 3. Конечно, он будет работать быстрее. Но получаете ли вы такой же результат? :-)

Paul Spiegel 16.05.2018 20:00

@PaulSpiegel, ха-ха, как раз собирался исправить себя, был слишком взволнован результатом и даже не потрудился проверить результат ... Я не получаю того же результата. Есть ли способ сделать что-то подобное, чтобы избежать подготовки значений ~ 6k?

shreddish 16.05.2018 20:07

Возникла проблема с PDO. Вот связанный с этим нерешенный вопрос: stackoverflow.com/questions/4350718/… .. Понятно - вы это уже нашли.

Paul Spiegel 16.05.2018 20:08

@PaulSpiegel Ох уж совсем забыл об этом ... Это продолжающийся процесс оптимизации, который уже несколько месяцев откладывается на задний план, и, погрузившись в него, я явно забыл о своих предыдущих исследованиях. Отметить как дубликат или удалить это?

shreddish 16.05.2018 20:17

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

Paul Spiegel 16.05.2018 20:23

@PaulSpiegel, вы довольно активно ответили на другой вопрос. Есть ли у вас какие-либо рекомендации по решению этой проблемы? Я попытался удалить предложение IN и использовал WHERE pId =? ИЛИ pId =? ... ', но время не улучшилось. Кажется, что единственные другие варианты, если я хочу сохранить подготовленные операторы, - это переключиться на mysqli или разбить мои запросы.

shreddish 16.05.2018 22:11

@shreddish Вот связанный отчет об ошибке: bugs.php.net/bug.php?id=53458

Paul Spiegel 17.05.2018 19:47

@shreddish Обходной путь: если идентификаторы поступают из БД - используйте JOIN или IN (<subquery>). Если нет - сначала сохраните их во временной таблице.

Paul Spiegel 17.05.2018 20:14
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
3
22
154
1

Ответы 1

Я подозреваю, что медлительность связана с получением строк, количеством возвращаемых строк, а не с 5000+ заполнителями привязки в инструкции. pId IN ( ? , ? , ... , ? )

Мое предложение состояло в том, чтобы протестировать возврат только одной строки, предоставить одно значение, которое, как известно, существует / вернуть строку, а затем 4999+ значений, которые, как известно, не существуют / не возвращают строку.

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

 ... pId IN ( ? , ? , ? , ... , ? )

поэтому результат будет эквивалентен запуску

 ... pId IN ( 99999999 , 99999998 , 99999997 , ... , 42 )

что будет тем же результатом, что и мы

 ... pId IN ( 42 )

Мы ожидаем вернуть только одну строку (pId = 42).

Затем сравните время этого (5000+ значений привязки, возвращающих 1 строку) с двумя значениями привязки, возвращающими одну строку

 ... pId IN ( 99999999 , 42 )

И посмотрите, есть ли существенная разница в производительности.

(С более чем 5000 значений привязки предстоит еще поработать, но я бы не ожидал разницы в огромный, но ее следует протестировать.


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

Может быть, лучше добавить условие вроде AND pId * 10 = 420

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


Другой тест - вернуть множество строк, но с использованием только пары значений привязки. Может быть, условие диапазона, которое возвращает 5000+ строк.

Запрос может быть таким:

 ... pId >= ? AND pId <= ? 

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

И сравните производительность.

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


Я не уверен, что это ответ на ваш вопрос, но это подход, который я бы выбрал, чтобы ответить на вопрос ... «что вызывает медленную работу, количество значений привязки или количество возвращаемых строк? "

"что заставляет это быть медленным, количество значений привязки или количество возвращаемых строк?" - Это и то, и другое - таким образом, O (n²)

Paul Spiegel 17.05.2018 20:09

@PaulSpiegel: будет подтверждено некоторым тестированием ... я подозреваю, что количество значений привязки не так сильно влияет на общее прошедшее время. Тесты, которые я рекомендую в этом ответе, должны указывать, какой фактор (количество значений привязки, количество или возвращаемых строк) действительно влияет на прошедшее время. (Я предполагаю, что значения привязки предоставляются с bindValue, а не с bindParam, или, может быть, это не имеет значения.)

spencer7593 17.05.2018 21:26

Мой комментарий основан на тестах. Выполните глупый тестовый запрос вроде select * from t where id in (?, .. , ?) or 1=1 limit $numRows. Начните с 1000 параметров и ограничьте 1000. Затем увеличьте количество параметров. Вы увидите увеличение времени выборки с тем же набором результатов.

Paul Spiegel 18.05.2018 13:57

@PaulSpiegel: а тесты проводятся с использованием bindValue, а не bindParam, верно?

spencer7593 18.05.2018 15:49

Они выполняются с использованием exec($params). Но как вы думаете, почему это имеет значение? Большую часть времени проводят с fetch() или fetchAll().

Paul Spiegel 18.05.2018 16:17

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