Николай Ланец
12 мая 2017 г., 7:27

Производительность SQL-запросов на больших сайтах

Продолжение к этой статье: https://modxclub.ru/topics/tegi-dlya-krupnogo-sajta-2595.html (вынес и расширил комментарий). Информация по поводу производительности SQL-запросов на больших сайтах.

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


Так вот, даже после оптимизации страницы, настройке вторичных ключей и т.п., всего при одном джоине со связью один-к-одному, выборка с подсчетом строк занимает несколько секунд.
Total time: 5.6790 s
Даже оптимизировав getdata-процессор и исключив один запрос из трех, я все равно получаю довольно нерадостный результат.
Total time: 0.6432 s
А вот если я исключу и запрос подсчета количества записей, то результат уже вот такой:
Total time: 0.0036 s
Заметьте, что это еще с учетом выполнения консоли, обработкой данных на уровне PHP и т.п. То есть реально запрос выполняется где-то в районе 0.0003 сек.

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

Кстати, в MODX-процессорах (которые и getdata-процессор наследует), изначально было два полезных метода: prepareQueryBeforeCount() и prepareQueryAfterCount(). В getdata-процессорах изначально был добавлен еще один метод - setSelection(). Хотя он, по сути, дополняет метод prepareQueryAfterCount() и вполне можно обойтись без него, все-таки он по названию как-то боле логичный и традиционно использовался для добавления select-конструкций в основной запрос. Сейчас объясню логику.
Вот возьмем, к примеру, основную таблицу документов. Когда вы просто формируете запрос из одной только его таблицы, даже если вы добавили какие-нибудь условия выборки, если на эти колонки созданы корректные индексы и БД использовала их для запроса, при подсчете количества записей скорее всего реального обращения к диску не будет, то есть кол-во строк БД подсчитает из индексов. В таком случае запрос на подсчет пройдет мгновенно. (Это область действия в рамках методе prepareQueryBeforeCount() и выполняемого за ним запроса на подсчет строк). А вот если вам надо получить данные из дополнительных таблиц (с использованием join-ов), то лучше их добавить уже в методе prepareQueryAfterCount() (или в случае использования getdata-процессоров в метод setSelection(), просто для наглядности). Вот тогда даже при использовании джоинов, запросы пройдут быстрее, так как не будет в этот момент выполняться подсчета строк.
Настроил теги. Все работает. Спасибо за наводку. По поводу индексов: планирую делать выборку по тегам через pdoResources с условием

&where=`["1 = 1 AND FIND_IN_SET('Мой тег', tags)"]`

Поможет ли в этом случае следующий индекс?

CREATE INDEX tags ON modx_site_content(tags);

Или нужно как-то иначе индекс задать?
Не думаю, что в таком случае индекс как-то спасет. Если бы поле было ENUM (где четкий набор значений указывается), там можно было бы индекс использовать. А здесь вряд ли что-то ощутимое будет. Но это не 100%, не на столько хорошо я мускул знаю, а специальные исследования на этот счет я не проводил.
Благодарю за ответы!

Добавить комментарий