Pull to refresh

Comments 38

Вы просто невероятный извращенец. Такое впечатление, что у вас там 2-3 магазина, и в каждом по 100 товаров. Т.к. иначе запросы у вас будут выполняться по много секунд, и это просто недопустимо для выполнения во время загрузки страницы.
Возможно, вы это делаете раз в 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()? Это примерно так же медленно будет

примерно так-же медленно, но не вернет нужного результата
Есть такие оконные функции. Изучите. Они много умеют.
ps: Согласен с остальными. Изврату не место в prod-е. Проблема в логике задачи.

Если бы они еще в mysql были… К сожалению, самое близкое, что есть в mysql — это group_concat.

В таблице с магазинами заведите поле для последнего сгенрированного Вами идентификатора товара в рамках конкретного магазина, а в таблице с товаром заведите поле для нумерации товара в пределах одного магазина и при вставке заполняйте его порядковым номером, и последний номер сохраняйте в поле таблицы магазинов.


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

Тогда выборка будет всегда одинаковая. Товары будут всегда иметь постоянный порядковый номер и выводиться на одном месте.
А показываться должны случайные товары
Можно добавить в таблицу с магазинами и в таблицу с товарами по столбцу, где будут хранится рандомные числа. Соответственно заполняться они должны при создании магазина и товара. А потом сортируйте по этим полям. И пагинацию можно использовать.
Опять же — тогда выборка будет всегда одинаковая. Нужны именно случайные товары
О какой пагинации тогда речь? Нужно фиксировать какой-то интервал времени, чтобы можно было использовать пагинацию (час, день, неделя)
Случайные товары на главной… Изначально выводятся 20 к примеру. При прокручивании страницы — добавляются еще 20 и т.д…
Получается бесконечный скроллинг из случайных товаров. Причем так, чтоб товары одного магазина не шли подряд.
На самом деле выборка будет случайной если грамотно использовать имеющиеся в таблицах рандомные числа. Как вариант — перед выборкой генерируете случайный диапазон (фиксированного размера), в который попадет только часть рандомных чисел. Это работает довольно быстро.
Опять-же, выборка будет довольно статичной.
Согласен, если пронумеровать товары при добавлении, а потом выбирать диапазон — можно получить список товаров, удовлетворяющий условию, по которому магазины не должны повторяться. Но порядок в таком случае поять-же будет всегда одинаковый…
Пронумеровать — это если записи не будут удаляться. Если нужно равномерное распределение — лучше использовать рандомные числа.
Как бонус получаете работающую пагинацию, просто используете сгенерированный ранее диапазон и значения повторятся уже не будут.
Но последовательность всегда будет одинакова…
т.е. если товары идут в порядке 1,4,3,5,12,36,51,10,22

То порядок будет всегда такой…
4,3,5 или 3,5,12 или 1,4,3…

Т.е. порядок не перемешивается…

По чесноку, с первых строк задача вызвала недоумение, дочитал по диагонали, наблюдая простыни запросов, и минуты времени на их выполнение. Это опять же вызвало недоумение. Вообще у меня в это утро много чего вызывает недоумение Можете объяснить несколько подробнее преследуемые цели? Конечно, умные функции в запросах это круто, но забивать гвозди пневмопрессом — это перебор(А может тут все же и не гвозди, вот я и хочу разобраться).

Ну суть проста — есть N магазинов, есть по M товаров в каждом.

На главной странице выводятся случайные товары.
Но получается так, что если у одного магазина 30 товаров, а у остальных по 5 (грубо говоря), то на главной странице может висеть половина товаров из одного магазина.

Чего и надо избежать.

Т.е. несмотря на кол-во товаров в магазине (1000 или 10) их необходимо вставлять в топ 20 с одинаковой вероятностью.

А что за группы товаров? Это топы по популярности внутри магазина/в целом, либо это топы по определенной группе товаров (опять же внутри магазина/в целом), либо что? Просто мне видится более логичным, раз уж нужны рандомные магазины, то и выбирать рандомные магазины, а там и товары джойнить.

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

Тогда в чем проблема записывать все айди товаров, доступных в данном магазине в отдельную таблицу; при обращении, средствами платформы вывести N неравных, рандомных айдишников магазинов — столько, сколько нужно, для страницы, либо для списка в целом, если кешировать. И потом, с помощью той же платформы получить айдишник товара в диапазоне от 0 до количества доступных товаров, для данного магазина — замассивить эти айдишники, и потом дернуть одним запросом. ~20 строк кода, перфоманс++, все как надо, просто и лаконично, порчу на приход новых разработчиков не наводит.

Согласен на счет популярных.

А также можно было счетчик добавить в таблицу товаров.
И на счет популярных, и на счет счетчика в таблице я ответил выше.
1. В выборке нужны НЕ популярные товары, а случайные
2. Счетчик в таблице делает выборку статичной, а не случайной.
Думаю, счетчик можно сделать так, чтобы выборка была не статичной.
UFO just landed and posted this here
А Вы правильно смотрели скорость выполнения запроса? Делали EXPLAINE и SQL_NO_CACHE?
Предполагаю что, те цифры которые вы увидели при выполнении запросов, это цифры уже загруженным кешем от предыдущего выполнения этих же запросов.

Но даже если закрыть на это глаза в выборке 100 магазинов, 1000000 товаров — ~2568ms (2.568s) то есть на незначительном количестве данных вы имеете УЖЕ 2 секунды просто на получение данных. Что не приемлемо в принципе.

Впрочем, Вам конечно виднее, и возможно вы никогда не выйдете за рамки 10 магазинов.

UFO just landed and posted this here
За скорость — честно не смотрел через експлейн… Просто посмотрел, как шторм ответил. + это на локальной машине, а не на сервере.
Любителям извращений можно ещё и с GROUP_CONCAT + FIND_IN_SET поиграться.
А почему нельзя зарезервировать для каждого магазина определённое количество мест, а затем из каждой базы случайным образом выбирать товары?
Неизвестно кол-во магазинов, неизвестно кол-во мест… Т.е. товары идут подряд пока не кончатся.
Я уже ответил на этот вопрос выше…
Пагинировать лучше всего, кешируя только последовательность ID товаров в каком-нибудь Redis. Предварительно к сессии привяжи этот токен.

Я возможно не до конца понял, что вам надо, но в первую очередь в голову пришел следующий подход.


При добавлении товара всегда проставлять ему номер по порядку в рамках конкретного магазина. Это относительно дешево. В таблице магазинов держать максимальный номер по порядку для каждого магазина.


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


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


Построить индекс shop_id, index по таблице products. Сделать в неё запрос вида SELECT… FROM products WHERE (shop_id, index) IN((X1, I1), (X2, I2), ...), где Xn и In — идентификатор n-ого магазина и сгенерированный для него случайный номер по порядку. Запрос должен быть дешевым.


Вывести это на страницу. В ссылке на вторую страницу передать прочитанное состояние сида. На второй странице сидировать им, а не случайным.


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


Скорее всего есть решение проще.

CrazyXoma случая выборка получается запросами к случайным записям, то есть
LIMIT 1,random_offset

В WHERE можно задать магазин, если запрос будет параметризированным, то с его отработкой (быстродействием) не будет проблем.

Хотя конечно если между сервером приложений и сервером СУБД сетевой обмен на один запрос занимает сотни миллесекунд, то время на то что бы надёргать 20 записей, уйдёт больше чем если один раз дёрнуть всё выборку пронумерованную вашим способом :)

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

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

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
Sign up to leave a comment.

Articles