Comments 38
Возможно, вы это делаете раз в 10 минут и кешируете, а при загрузке просто берете подготовленные данные из кеша — но все равно это очень-очень плохой способ.
Я вам не предложу взамен готового решения, т.к. нет условий задачи. С моей точки зрения, перебирать все товары — уже очень плохо, перемешивать все товары — еще хуже (представьте, что их там несколько миллиардов).
Скорее всего показывать на главной нужно самые популярные товары, а популярность у вас 100% обсчитывается по крону и пишется в отдельную табличку — и не важно на самом деле, что случайно попадет 2-3 товара из всей пачки из 1 магазина.
Если же магазинов много (больше, чем товаров), то при обсчете популярности проще ы shops добавить колонку most_popular_product_id и обновлять ее, а затем выбирать случайные 20 магазинов с их самыми популярными продуктами.
Если же вам нужен полный рандом среди абсолютно всех товаров, то зачем городить огород, если можно (это ужасно, но на порядок лучше того, что сделали вы) тогда select * from products order by rand() limit 20;
И это полный рандом, с распределением среди магазинов, пропорциональным количеству товаров у них.
Вы просто невероятный извращенец.
Я буду считать это за комплимент =)
А по сути:
Такое впечатление, что у вас там 2-3 магазина, и в каждом по 100 товаров. Т.к. иначе запросы у вас будут выполняться по много секунд, и это просто недопустимо для выполнения во время загрузки страницы.
Я при вел пример, что для 10 магазинов и 10000 товаров запрос выполняется 0.056с.
Скорее всего показывать на главной нужно самые популярные товары, а популярность у вас 100% обсчитывается по крону и пишется в отдельную табличку
Нет, нужны именно все товары в случайном порядке
и не важно на самом деле, что случайно попадет 2-3 товара из всей пачки из 1 магазина
Именно в этом и состояла задача, чтоб не попали 2-3 товара из 1-го магазина
И это полный рандом, с распределением среди магазинов, пропорциональным количеству товаров у них.
Опять же — получается, что в выборке будут попадаться товары одного магазина.
Ну и в заключении напишу, что это все таки пока теоретические изыскания =)
Order by rand()? Это примерно так же медленно будет
В таблице с магазинами заведите поле для последнего сгенрированного Вами идентификатора товара в рамках конкретного магазина, а в таблице с товаром заведите поле для нумерации товара в пределах одного магазина и при вставке заполняйте его порядковым номером, и последний номер сохраняйте в поле таблицы магазинов.
Получится то же эффект что и у Вас в статье, только генерировать Вы будете не на этапе выборки, а на этапе вставки. Что при выборке даст высочайшую производительность.
Согласен, если пронумеровать товары при добавлении, а потом выбирать диапазон — можно получить список товаров, удовлетворяющий условию, по которому магазины не должны повторяться. Но порядок в таком случае поять-же будет всегда одинаковый…
Как бонус получаете работающую пагинацию, просто используете сгенерированный ранее диапазон и значения повторятся уже не будут.
По чесноку, с первых строк задача вызвала недоумение, дочитал по диагонали, наблюдая простыни запросов, и минуты времени на их выполнение. Это опять же вызвало недоумение. Вообще у меня в это утро много чего вызывает недоумение Можете объяснить несколько подробнее преследуемые цели? Конечно, умные функции в запросах это круто, но забивать гвозди пневмопрессом — это перебор(А может тут все же и не гвозди, вот я и хочу разобраться).
На главной странице выводятся случайные товары.
Но получается так, что если у одного магазина 30 товаров, а у остальных по 5 (грубо говоря), то на главной странице может висеть половина товаров из одного магазина.
Чего и надо избежать.
Т.е. несмотря на кол-во товаров в магазине (1000 или 10) их необходимо вставлять в топ 20 с одинаковой вероятностью.
А что за группы товаров? Это топы по популярности внутри магазина/в целом, либо это топы по определенной группе товаров (опять же внутри магазина/в целом), либо что? Просто мне видится более логичным, раз уж нужны рандомные магазины, то и выбирать рандомные магазины, а там и товары джойнить.
Т.е. список случайных товаров всех магазинов. Но так, чтоб магазины не шли друг за другом
Тогда в чем проблема записывать все айди товаров, доступных в данном магазине в отдельную таблицу; при обращении, средствами платформы вывести N неравных, рандомных айдишников магазинов — столько, сколько нужно, для страницы, либо для списка в целом, если кешировать. И потом, с помощью той же платформы получить айдишник товара в диапазоне от 0 до количества доступных товаров, для данного магазина — замассивить эти айдишники, и потом дернуть одним запросом. ~20 строк кода, перфоманс++, все как надо, просто и лаконично, порчу на приход новых разработчиков не наводит.
А также можно было счетчик добавить в таблицу товаров.
Предполагаю что, те цифры которые вы увидели при выполнении запросов, это цифры уже загруженным кешем от предыдущего выполнения этих же запросов.
Но даже если закрыть на это глаза в выборке 100 магазинов, 1000000 товаров — ~2568ms (2.568s) то есть на незначительном количестве данных вы имеете УЖЕ 2 секунды просто на получение данных. Что не приемлемо в принципе.
Впрочем, Вам конечно виднее, и возможно вы никогда не выйдете за рамки 10 магазинов.
Я возможно не до конца понял, что вам надо, но в первую очередь в голову пришел следующий подход.
При добавлении товара всегда проставлять ему номер по порядку в рамках конкретного магазина. Это относительно дешево. В таблице магазинов держать максимальный номер по порядку для каждого магазина.
Кэшировать соответствие магазин — максимальный номер по порядку. Если магазинов не планируется в ближайшее время тысячи, то тоже относительно дешево. Поскольку изменения происходят редко, то консистентность кэша достаточно поддерживать просто протуханием через некоторое время — новый товар не будет отображаться лишь это время.
Выбрать генератор случайных чисел, у которого можно независимо от других его вызовов (условно, вроде mt_rand из PHP не подойдет) читать и писать состояние (сид фактически). Для первой страницы генерировать случайный сид и писать его в генератор. Для каждого магазина (ну или для нужного количества, если на странице надо показать меньше записей, чем всего магазинов) генерировать число от 1 до максимального номера по порядку этого магазина (см. кэш выше). После генерации достаточного количества прочитать состояние сида.
Построить индекс shop_id, index по таблице products. Сделать в неё запрос вида SELECT… FROM products WHERE (shop_id, index) IN((X1, I1), (X2, I2), ...), где Xn и In — идентификатор n-ого магазина и сгенерированный для него случайный номер по порядку. Запрос должен быть дешевым.
Вывести это на страницу. В ссылке на вторую страницу передать прочитанное состояние сида. На второй странице сидировать им, а не случайным.
Оставшаяся проблема: товары будут повторяться на последующих страницах, когда генератор будет создавать одно и то же случайное число на разных страницах.
Скорее всего есть решение проще.
LIMIT 1,random_offset
В WHERE можно задать магазин, если запрос будет параметризированным, то с его отработкой (быстродействием) не будет проблем.
Хотя конечно если между сервером приложений и сервером СУБД сетевой обмен на один запрос занимает сотни миллесекунд, то время на то что бы надёргать 20 записей, уйдёт больше чем если один раз дёрнуть всё выборку пронумерованную вашим способом :)
Задать веса магазинам, в зависимости от количества товара в каждом (больше товара — реже встречается). Так вы избежите ситуации "на странице товара одного магазина". Использовать вес для рандома. Только не надо настоящий рандом — он медленный. Надо его симулировать. Вообще, если честно, задача кажется надуманной, но не буду с вами спорить.
SET @i=0;
SET @currentShop=0;
SELECT id, shop_id,
@i:= CASE WHEN @currentShop = shop_id THEN @i + 1 ELSE 1 END AS numCol,
@currentShop := shop_id AS currentShop FROM (
SELECT id, shop_id FROM products ORDER BY shop_id, rand()
) AS randT ORDER BY numCol, shop_id
С пагинацией сложно, хранить свою выборку для каждого пользователя — накладно. Хранить одну на всех — возможно.
Если на каждой страницы вытаскивать новые записи, то это сильно ускорит запрос, но товары начнут повторяться.
P.S. Думаю стоило данный пост разместить на toster.ru или stackoverflow.com
Чередование выборки в MySQL