За последние 24 часа нас посетил 15551 программист и 1492 робота. Сейчас ищут 904 программиста ...

Pagination - опасность Limit offset (чем заменить?)

Тема в разделе "PHP для новичков", создана пользователем glorsh66, 1 май 2018.

  1. glorsh66

    glorsh66 Активный пользователь

    С нами с:
    9 июл 2017
    Сообщения:
    247
    Симпатии:
    4
    Я собственно пишу свою мини либы, в которой мне потребовалось организовать пагинацию (приватные сообщения, и если количество переписок превышает разумные границы, сделать перенос на следующую страницу)

    Но начитавшись умных статей - я узнал, что оказывается команда
    Код (Text):
    1. limit ofsset
    очень сильно просаживается с увеличением количества записей.
    Вопрос чем её тогда заменить?

    Как её заменяют в популярных приложениях ( например этот форум?)
     
  2. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.591
    Симпатии:
    1.763
    Первый раз слышу. Ссылку на умную статью можно? Люди, которые такие вещи пишут, обычно указывают и чем заменить.

    Ничем не заменить, при большом количестве записей и большой нагрузке кэшировать результаты выборок и всё.
     
    glorsh66 нравится это.
  3. glorsh66

    glorsh66 Активный пользователь

    С нами с:
    9 июл 2017
    Сообщения:
    247
    Симпатии:
    4
    --- Добавлено ---
    https://ruhighload.com/Постраничный+вывод+в+mysql

    Это первая статья.
    Еще парочка, в закладках на другой машине у меня, как доберусь и если не забуду скину, но там тот же самый смысл.
     
  4. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.847
    Симпатии:
    653
    Там же написано, что можно заменить условием с id первой записи на странице, который вы передаете в адресе.

    Правда, тут есть риск, что над вашим сайтом начнут издеваться, проставляя ссылки с id записи из середины какой-то страницы.

    P.S. count часто кэшируем триггерами в записи самого списка. И это не из-за InnoDB, который в основном и используем, а чтобы в принципе не выполнять лишний запрос. Плюс это значение часто требуется в метаданных и без формирования самого списка.
     
  5. glorsh66

    glorsh66 Активный пользователь

    С нами с:
    9 июл 2017
    Сообщения:
    247
    Симпатии:
    4
    Да, но тут беда в том, что могут быть ID не попорядку, а именно с дырками (и вообще как бы порядок ID не рекомендуется использовать как основу, для сортировки и т.д.)

    Как это кэшируем тригерррами в записи самого списка? Это как?
     
  6. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.847
    Симпатии:
    653
    Тут смысл не в том, чтобы использовать id вместо нумерации. Важен только хронологический порядок, т.е. предполагается что чем больше id, тем позднее создана соотв. запись.

    Поставьте триггеры на добавление и удаление записи, в которых обновляйте счетчик(и) родительских по отношению к элементам данного списка записей.
     
    #6 miketomlin, 1 май 2018
    Последнее редактирование: 1 май 2018
  7. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.847
    Симпатии:
    653
    Кстати, а это где написано? Обычно наоборот пытаются сделать оптимизацию с использованием этого поля, чтобы не индексировать поле с временем.
    --- Добавлено ---
    Ну если оч. хоЦА, можете в качестве базы для постраничной навигации использовать время :)
     
  8. glorsh66

    glorsh66 Активный пользователь

    С нами с:
    9 июл 2017
    Сообщения:
    247
    Симпатии:
    4
    Кстати вот вспомнил умные статьи -
    и вот какое лечение они предлагают

    https://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/

    Код (Text):
    1. SELECT  l.id, value, LENGTH(stuffing) AS len
    2. FROM    (
    3.         SELECT  id
    4.         FROM    t_limit
    5.         ORDER BY
    6.                 id
    7.         LIMIT 150000, 10
    8.         ) o
    9. JOIN    t_limit l
    10. ON      l.id = o.id
    11. ORDER BY
    12.         l.id
    По сути - внутренний запрос только по индесированному полю и к этому результату уже join
     
  9. nospiou

    nospiou Старожил

    С нами с:
    4 фев 2018
    Сообщения:
    3.400
    Симпатии:
    510
    ну.. редис же мемкешед что за костыли) https://redis.io/ mysql не дураки писали.
     
    glorsh66 нравится это.
  10. glorsh66

    glorsh66 Активный пользователь

    С нами с:
    9 июл 2017
    Сообщения:
    247
    Симпатии:
    4
    Кстати только что протестировал дает достаточно существенную разницу даже после 100.000 записей

    Она особенно видна становится когда сильно вырастает offset
    limit 10 offset 10000

    А чет тут redis.io поможет?
     
  11. nospiou

    nospiou Старожил

    С нами с:
    4 фев 2018
    Сообщения:
    3.400
    Симпатии:
    510
    Хм. Ты же знаешь что такое индексы?
     
    glorsh66 нравится это.
  12. glorsh66

    glorsh66 Активный пользователь

    С нами с:
    9 июл 2017
    Сообщения:
    247
    Симпатии:
    4
    Разбираюсь сейчас с их тонкостями☻
    Особенно тонкостями составных индексов)
     
  13. glorsh66

    glorsh66 Активный пользователь

    С нами с:
    9 июл 2017
    Сообщения:
    247
    Симпатии:
    4
  14. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.129
    Симпатии:
    1.249
    Адрес:
    там-сям
    С LIMIT offset, number тормоза возникают на больших offset. Это происходит потому, что MySQL вынужден пролистать все записи до offset чтобы в итоге выдать тебе только нужный кусочек.
    Выхода два:

    1. Найти способ заменить его на LIMIT number. То есть без смещения. Вместо него добавляем условие в WHERE и желательно по индексированному полю(ям). Это то, о чём писал @miketomlin "…можно заменить условием с id первой записи на странице" — не обязательно id, используем то поле(я), по которому(ым) идет сортировка в этой выдаче.

    2. Сохранить LIMIT offset, number, но уменьшить объем данных, которые MySQL будет перебирать, отсчитывая записи до offset. То есть упростить до предела, избавиться от некритичных для счетчика джойнов и лишних полей. Оставить во фразе select только id, например.
    Используем этот упрощенный запрос как под-запрос и уже во внешнем запросе добавляем все оставшиеся таблицы и поля. Это то, что ты @glorsh66 нашел в этом коменте. Я только сформулировал в общем виде.
    --- Добавлено ---
    Я не вникал как это в движке Xenforo который установлен здесь, но я знаю как это решено в семействе форумов PunBB/FluxBB — по второму варианту. Используется облегчённый быстрый запрос, а найденные ID уже подставляются в главную выборку.

    https://github.com/fluxbb/fluxbb/blob/master/viewtopic.php#L217
     
    glorsh66 нравится это.
  15. glorsh66

    glorsh66 Активный пользователь

    С нами с:
    9 июл 2017
    Сообщения:
    247
    Симпатии:
    4
    Благодарю за отличный ответ!☻
    --- Добавлено ---
    Небольшой оффтоп ☻
    На гитхабе такая строчка -
    Код (Text):
    1. $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id='.$id.' ORDER BY id LIMIT '.$start_from.','.$pun_user['disp_posts']) or error('Unable to fetch post IDs', __FILE__, __LINE__, $db->error());
    Но что такое
    Код (Text):
    1. or error
    Что это за конструкция такая?
     
  16. Sail

    Sail Старожил

    С нами с:
    1 ноя 2016
    Сообщения:
    1.593
    Симпатии:
    362
  17. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    чувак, это хайлоад. не надо туда лезть. там твою либу один хрен юзать не будут никогда.
     
    #17 igordata, 10 май 2018
    Последнее редактирование: 10 май 2018
  18. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.129
    Симпатии:
    1.249
    Адрес:
    там-сям
    PHP:
    1. blablabla() or unzununz();
    это работает так: если blablabla() выполнилось с непустым результатом (что приравнивается к значению true), то вторая функция выполняться не будет.
    но если blablabla() вернуло 0 | false | null, короче "пустое значение", то будет вызвано то, что правее "or".

    все методы объекта $db в той библиотеке возвращают непустой результат в случае успеха. то есть error() не должен выполняться по идее. error() выводит ошибку и заканчивается exit() чтобы дальше уже ничего не выполнялось.

    понятно?
     
    glorsh66 нравится это.
  19. glorsh66

    glorsh66 Активный пользователь

    С нами с:
    9 июл 2017
    Сообщения:
    247
    Симпатии:
    4
    Небольшой оффтоп ☻
    На гитхабе такая строчка -
    Код (Text):
    1. $db->query('SELECT id FROM '.$db->prefix.'posts WHERE topic_id='.$id.' ORDER BY id LIMIT '.$start_from.','.$pun_user['disp_posts']) or error('Unable to fetch post IDs', __FILE__, __LINE__, $db->error());
    Но что такое
    Код (Text):
    1. or error
    Да)
     
  20. nospiou

    nospiou Старожил

    С нами с:
    4 фев 2018
    Сообщения:
    3.400
    Симпатии:
    510
    Собрался прочел статью и как то странно все это всегда считал что офсет работает по принципу бинарного поиска.
     
    glorsh66 нравится это.
  21. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    если по индексу - то да

    поэтому суть статьи совсем не в том, чтобы отказаться от оффсета
    оффсет используется там где есть запрос, к запросу условие, к условию оффсет
    эссно это даёт необходимость построить всё услоие и все пересечения всех индексов где-то в памяти.

    суть статьи в том, чтобы так построить свою архитектуру, чтобы оффсет стал не нужен
    тогда можно будет выбирать по id или любому другому уникальному ключу.