Как расширить WP_Query для включения пользовательской таблицы в запрос?

26 апр. 2012 г., 15:49:13
Просмотры: 48.2K
Голосов: 39

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

id  leader_id   follower_id
1   2           4
2   3           10
3   2           10

В таблице выше, в первой строке пользователь с ID 2 отслеживается пользователем с ID 4. Во второй строке пользователь с ID 3 отслеживается пользователем с ID 10. Та же логика применяется и для третьей строки.

Теперь я хочу расширить WP_Query так, чтобы можно было ограничить выборку постов только теми, которые созданы лидером(ами) определенного пользователя. Таким образом, учитывая приведенную выше таблицу, если передать ID пользователя 10 в WP_Query, результаты должны содержать только посты пользователей с ID 2 и ID 3.

Я много искал, пытаясь найти ответ. Я не нашел ни одного учебника, который помог бы мне понять, как расширить класс WP_Query. Я видел ответы Майка Шинкеля (расширение WP_Query) на похожие вопросы, но я действительно не понял, как применить это к моим потребностям. Было бы здорово, если бы кто-нибудь мог помочь мне с этим.

Ссылки на ответы Майка по запросу: Ссылка 1, Ссылка 2

7
Комментарии

Добавьте, пожалуйста, ссылку на ответы Майка.

kaiser kaiser
26 апр. 2012 г. 15:57:41

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

mor7ifer mor7ifer
26 апр. 2012 г. 15:58:43

@kaiser Я обновил вопрос, добавив ссылки на ответы Майка.

John John
26 апр. 2012 г. 16:01:41

@m0r7if3r »Я хочу расширить WP_Query, чтобы можно было ограничить выборку постов только теми, которые созданы лидерами пользователя«, то есть аналогично "get posts by author".

kaiser kaiser
26 апр. 2012 г. 16:04:44

@John Проблема в следующем: как определить, кто на самом деле является лидером? Предполагаю, что можно следовать за несколькими людьми...

kaiser kaiser
26 апр. 2012 г. 16:05:35

@m0r7if3r Мне нужно именно запрашивать посты. Но выбирать следует только посты пользователей, которые указаны как лидеры определенного пользователя в кастомной таблице. Другими словами, я хочу сказать WP_Query: "получи все посты всех пользователей, которые перечислены как лидеры пользователя с ID '10' в кастомной таблице".

John John
26 апр. 2012 г. 16:08:46

@kaiser Безусловно, пользователь может подписаться на многих, и, следовательно, количество лидеров у пользователя может быть большим. Идея заключается в том, чтобы получить все записи всех лидеров пользователя. Я не уверен, работает ли SQL-запрос get_posts_by_author с несколькими авторами.

John John
26 апр. 2012 г. 16:15:01
Показать остальные 2 комментариев
Все ответы на вопрос 5
0
23

Важное предупреждение: правильный способ сделать это — НЕ изменять структуру таблицы, а использовать wp_usermeta. Тогда вам не понадобится писать собственный SQL для запроса постов (хотя всё равно потребуется некоторый кастомный SQL для получения списка всех, кто подчиняется определённому руководителю — например, в разделе админки). Однако, поскольку автор вопроса спрашивал о написании кастомного SQL, вот текущие лучшие практики для вставки собственного SQL в существующий запрос WordPress.

Если вы делаете сложные JOIN-запросы, недостаточно просто использовать фильтр posts_where, потому что вам также понадобится модифицировать JOIN, SELECT и, возможно, GROUP BY или ORDER BY части запроса.

Лучший вариант — использовать фильтр 'posts_clauses'. Это очень полезный фильтр (которым не стоит злоупотреблять!), позволяющий добавлять/изменять различные части SQL-запроса, автоматически генерируемого множеством строк кода в ядре WordPress. Сигнатура функции-колбэка для этого фильтра: function posts_clauses_filter_cb( $clauses, $query_object ){ }, и она ожидает, что вы вернёте $clauses.

Части запроса (Clauses)

$clauses — это массив, содержащий следующие ключи; каждый ключ представляет собой строку SQL, которая будет напрямую использована в итоговом SQL-запросе к базе данных:

  • where
  • groupby
  • join
  • orderby
  • distinct
  • fields
  • limits

Если вы добавляете таблицу в базу данных (делайте это только если совершенно невозможно использовать post_meta, user_meta или таксономии), вам, вероятно, понадобится изменить несколько этих частей, например, fields (часть "SELECT" в SQL), join (все ваши таблицы, кроме указанной в "FROM"), и, возможно, orderby.

Изменение частей запроса

Лучший способ сделать это — использовать ссылку на соответствующий ключ массива $clauses, полученного из фильтра:

$join = &$clauses['join'];

Теперь, если вы измените $join, вы напрямую измените $clauses['join'], поэтому изменения сохранятся в $clauses, когда вы его вернёте.

Сохранение оригинальных частей запроса

Скорее всего (нет, серьёзно, слушайте внимательно) вы захотите сохранить существующий SQL, сгенерированный WordPress. Если нет, вам, вероятно, стоит посмотреть на фильтр posts_request — это полный mySQL-запрос непосредственно перед отправкой в базу данных, так что вы можете полностью перезаписать его своим. Зачем это делать? Скорее всего, вам это не нужно.

Итак, чтобы сохранить существующий SQL в частях запроса, помните, что нужно дополнять их, а не перезаписывать (т.е. использовать $join .= ' {НОВЫЙ SQL}';, а не $join = '{ПЕРЕЗАПИСЬ SQL}';). Учитывайте, что каждый элемент массива $clauses — это строка, поэтому при дополнении лучше добавлять пробел перед другими символами, иначе вы можете получить синтаксическую ошибку SQL.

Вы можете просто предполагать, что в каждой части всегда что-то есть, и начинать каждую новую строку с пробела, например: $join .= ' my_table, или всегда добавлять пробел при необходимости:

$join = &$clauses['join'];
if (! empty( $join ) ) $join .= ' ';
$join .= "JOIN my_table... "; // <-- обратите внимание на пробел в конце
$join .= "JOIN my_other_table... ";


return $clauses;

Это скорее вопрос стиля. Главное помнить: всегда оставляйте пробел ПЕРЕД вашей строкой, если дополняете часть запроса, в которой уже есть SQL!

Собираем всё вместе

Первое правило разработки для WordPress — использовать как можно больше встроенных функций. Это лучший способ обеспечить совместимость в будущем. Предположим, команда разработчиков решит, что WordPress теперь будет использовать SQLite или Oracle или другой язык баз данных. Любой вручную написанный mySQL может стать недействительным и сломать ваш плагин или тему! Лучше позволить WordPress самому сгенерировать как можно больше SQL, а затем добавить только необходимые части.

Итак, первым делом нужно использовать WP_Query для генерации основной части вашего запроса. Точный метод зависит от того, где должен отображаться этот список постов. Если это часть страницы (не основной запрос), используйте get_posts(); если это основной запрос, можно использовать query_posts(), но правильнее перехватить основной запрос до отправки в базу данных (и потребления ресурсов сервера), используя фильтр request.

Итак, вы сгенерировали запрос, и SQL вот-вот будет создан. На самом деле, он уже создан, но ещё не отправлен в базу данных. Используя фильтр posts_clauses, вы добавите свою таблицу отношений сотрудников в запрос. Назовём эту таблицу {$wpdb->prefix} . 'user_relationship', и это таблица связей. (Кстати, я рекомендую сделать структуру этой таблицы более универсальной, превратив её в полноценную таблицу связей с полями: 'relationship_id', 'user_id', 'related_user_id', 'relationship_type'; это гораздо гибче и мощнее... но я отвлёкся).

Если я правильно понял, вы хотите передать ID лидера и затем видеть только посты его подчинённых. Надеюсь, я правильно понял. Если нет, вам придётся адаптировать сказанное под свои нужды. Я буду придерживаться вашей структуры таблицы: у нас есть leader_id и follower_id. Таким образом, JOIN будет выполняться по {$wpdb->posts}.post_author как внешнему ключу к 'follower_id' в вашей таблице 'user_relationship'.

add_filter( 'posts_clauses', 'filter_by_leader_id', 10, 2 ); // нам нужен 2, потому что мы хотим получить все аргументы

function filter_by_leader_id( $clauses, $query_object ){
  // Я не знаю, как вы планируете передавать leader_id, поэтому предположим, что это глобальная переменная
  global $leader_id;

  // В этом примере я хочу затронуть только запрос на главной странице.
  // Здесь используется $query_object, чтобы избежать влияния на ВСЕ запросы
  // (поскольку ВСЕ запросы проходят через этот фильтр)
  if ( $query_object->is_home() ){
    // Теперь добавим вашу таблицу в SQL
    $join = &$clauses['join'];
    if (! empty( $join ) ) $join .= ' '; // добавляем пробел только если нужно (для пущей важности!)
    $join .= "JOIN {$wpdb->prefix}employee_relationship EMP_R ON EMP_R.follower_id = {$wpdb->posts}.author_id";

    // И обязательно добавим это в наши критерии выборки
    $where = &$clauses['where'];
    // В любом случае, вы всегда начинаете с AND, потому что в WHERE всегда есть '1=1' в начале, добавленное WP.
    // Не забудьте ведущий пробел!
    $where .= " AND EMP_R.leader_id={$leader_id}"; // предполагаем, что $leader_id всегда (int)

    // И, предполагаю, вы хотите группировать посты по ID пользователя, так что модифицируем groupby
    $groupby = &$clauses['groupby'];
    // Нам нужно добавить в начало, так что...
    if (! empty( $groupby ) ) $groupby = ' ' . $groupby; // Для любителей выпендриваться
    $groupby = "{$wpdb->posts}.post_author" . $groupby;
  }

  // В любом случае, нам нужно вернуть наши части запроса...
  return $clauses;
}
2 мая 2012 г. 00:49:43
1
20

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

Большое спасибо @m0r7if3r и @kaiser за предоставление базовых решений, которые я смог расширить и реализовать в своем приложении. Этот ответ содержит подробности моей адаптации решений, предложенных @m0r7if3r и @kaiser.

Сначала позвольте объяснить, почему этот вопрос был задан. Из вопроса и комментариев к нему можно понять, что я пытаюсь заставить WP_Query получить записи всех пользователей (лидеров), за которыми следует данный пользователь (подписчик). Связь между подписчиком и лидером хранится в пользовательской таблице follow. Самое распространенное решение этой проблемы - извлечь ID пользователей всех лидеров подписчика из таблицы follow и поместить их в массив. Смотрите ниже:

global $wpdb;
$results = $wpdb->get_results($wpdb->prepare('SELECT leader_id FROM cs_follow WHERE follower_id = %s', $user_id));

foreach($results as $result)
    $leaders[] = $result->leader_id;

После получения массива лидеров его можно передать в качестве аргумента в WP_Query. Смотрите ниже:

if (isset($leaders)) $authors = implode(',', $leaders); // Необходимо, так как аргумент authors в WP_Query принимает только строку, содержащую ID авторов записей, разделенные запятыми

$args = array(
    'post_type'         => 'post',
    'posts_per_page'    => 10,
    'author'            => $authors
);

$wp_query = new WP_Query( $args );

// Продолжается обычный цикл WordPress

Вышеприведенное решение является самым простым способом достижения желаемых результатов. Однако оно не масштабируется. Как только у вас появится подписчик, следующий за десятками тысяч лидеров, результирующий массив ID лидеров станет чрезвычайно большим и заставит ваш сайт WordPress использовать 100-250 МБ памяти при каждой загрузке страницы и в конечном итоге приведет к сбою сайта. Решение проблемы заключается в том, чтобы запускать SQL-запрос непосредственно к базе данных и получать соответствующие записи. Именно тогда пришло на помощь решение @m0r7if3r. Следуя рекомендации @kaiser, я приступил к тестированию обеих реализаций. Я импортировал около 47 тысяч пользователей из CSV-файла для регистрации на свежей тестовой установке WordPress. На установке работала тема Twenty Eleven. После этого я запустил цикл for, чтобы около 50 пользователей следили за каждым другим пользователем. Разница во времени выполнения запросов для решений @kaiser и @m0r7if3r была поразительной. Решение @kaiser обычно занимало от 2 до 5 секунд на каждый запрос. Вариации, я полагаю, происходят из-за того, что WordPress кэширует запросы для последующего использования. С другой стороны, решение @m0r7if3r показало среднее время выполнения запроса 0,02 мс. При тестировании обоих решений индексирование для столбца leader_id было включено. Без индексирования наблюдалось значительное увеличение времени выполнения запроса.

Использование памяти при использовании решения на основе массива составляло около 100-150 МБ и снизилось до 20 МБ при выполнении прямого SQL-запроса.

Я столкнулся с проблемой в решении @m0r7if3r, когда мне нужно было передать ID подписчика в функцию фильтра posts_where. По крайней мере, насколько я знаю, WordPress не предоставляет средств для передачи переменной в функции фильтра. Вы можете использовать глобальные переменные, но я хотел избежать глобальных переменных. В итоге я расширил WP_Query, чтобы окончательно решить проблему. Итак, вот окончательное решение, которое я реализовал (на основе решения @m0r7if3r).

class WP_Query_Posts_by_Leader extends WP_Query {
    var $follower_id;

    function __construct($args=array()) {
        if(!empty($args['follower_id'])) {
            $this->follower_id = $args['follower_id'];
            add_filter('posts_where', array($this, 'posts_where'));
        }

        parent::query($args);
    }

    function posts_where($where) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'follow';
        $where .= $wpdb->prepare(" AND post_author IN (SELECT leader_id FROM " . $table_name . " WHERE follower_id = %d )", $this->follower_id);
        return $where;
    }
}


$args = array(
    'post_type'         => 'post',
    'posts_per_page'    => 10,
    'follower_id'       => $follower_id
);

$wp_query = new WP_Query_Posts_by_Leader( $args );

Примечание: В итоге я протестировал вышеприведенное решение с 1,2 миллиона записей в таблице follow. Среднее время выполнения запроса составило около 0,060 мс.

22 мая 2012 г. 10:31:26
Комментарии

Я никогда не говорил вам, как сильно я ценю обсуждение этого вопроса. Теперь, когда я понял, что пропустил его, я добавил голос "за" :)

kaiser kaiser
28 февр. 2014 г. 23:44:04
11

Вы можете реализовать это полностью на SQL с помощью фильтра posts_where. Вот пример такого решения:

if( some condition ) 
    add_filter( 'posts_where', 'wpse50305_leader_where' );
    // примечание: ID вопроса одинаково читается в обе стороны

function wpse50305_leader_where( $where ) {
    $where .= $GLOBALS['wpdb']->prepare( ' AND post_author '.
        'IN ( '.
            'SELECT leader_id '.
            'FROM custom_table_name '.
            'WHERE follower_id = %s'.
        ' ) ', $follower_id );
    return $where;
}

Я думаю, что есть способ сделать это с помощью JOIN, но я пока не придумал как. Я продолжу экспериментировать и обновлю ответ, если найду решение.

Как альтернативу, как предложил @kaiser, вы можете разделить это на две части: получение лидеров и выполнение запроса. У меня есть ощущение, что это может быть менее эффективно, но определенно более понятный способ. Вам нужно будет самостоятельно проверить эффективность, чтобы определить какой метод лучше, так как вложенные SQL-запросы могут работать довольно медленно.

ИЗ КОММЕНТАРИЕВ:

Функцию следует разместить в файле functions.php, а вызов add_filter() делать непосредственно перед вызовом метода query() в WP_Query. Сразу после этого следует вызвать remove_filter(), чтобы это не повлияло на другие запросы.

26 апр. 2012 г. 16:40:48
Комментарии

Отредактировал ваш вариант A и добавил prepare(). Надеюсь, вы не против правки. И да: Производительность должен измерить сам автор вопроса. В любом случае: я всё ещё считаю, что это должно быть просто usermeta и ничего больше.

kaiser kaiser
26 апр. 2012 г. 16:53:58

@m0r7if3r Спасибо за попытку предложить решение. Я только что оставил комментарий в ответ на ответ kaiser, с опасениями по поводу возможных проблем с масштабируемостью. Пожалуйста, примите это во внимание.

John John
26 апр. 2012 г. 16:54:08

@kaiser Ни капли не против, наоборот, я скорее благодарен :)

mor7ifer mor7ifer
26 апр. 2012 г. 16:55:21

@m0r7if3r Спасибо. Наличие таких ребят, как ты, в сообществе — это просто супер :)

kaiser kaiser
26 апр. 2012 г. 16:59:43

@m0r7if3r Тестировал это и обнаружил, что из-за этого WP_Query возвращает все записи. Под "всеми записями" я имею в виду записи всех типов (посты, вложения, страницы) всех пользователей. Вот SQL, который выводит echo $wp_query->request; для запроса SQL_CALC_FOUND_ROWS cs_posts.* FROM cs_posts INNER JOIN cs_postmeta ON (cs_posts.ID = cs_postmeta.post_id) WHERE 1=1 GROUP BY cs_posts.ID ORDER BY cs_posts.post_date DESC LIMIT 0, 30

John John
26 апр. 2012 г. 22:17:48

Я забыл добавить return $where;. Исправляет ли обновленный код эту проблему?

mor7ifer mor7ifer
26 апр. 2012 г. 22:33:34

Уф! Опечатки. Да, теперь работает. Попробую реализовать решение от @kaiser и замерить время выполнения. Кстати, побочный эффект, который я заметил при использовании этого метода — он начинает влиять на все запросы, если их несколько на одной странице. Кроме того, у меня сломались URL-перезаписи, когда код был помещен в function.php. Перенос кода непосредственно перед вызовом WP_Query устранил проблему с перезаписями URL. Хотя не уверен, является ли размещение кода перед WP_Query плохой практикой???

John John
26 апр. 2012 г. 23:14:55

Вам следует поместить функцию в ваш functions.php и добавить фильтр через add_filter() непосредственно перед вызовом метода query() в WP_Query. Сразу после этого следует удалить фильтр через remove_filter(), чтобы он не влиял на другие запросы. Не уверен, в чем может быть проблема с перезаписью URL — я много раз использовал posts_where и никогда не сталкивался с этим...

mor7ifer mor7ifer
26 апр. 2012 г. 23:21:42

@m0r7if3r Спасибо за подсказку. Продолжаю тестирование на тестовых данных. Буду держать в курсе.

John John
26 апр. 2012 г. 23:34:06

@m0r7if3r Ваш последний комментарий мог бы стать отличной цитатой внутри Q :)

kaiser kaiser
27 апр. 2012 г. 14:30:27

Это определенно лучшее решение. Отличная работа!

Travis van der Font Travis van der Font
5 окт. 2015 г. 16:58:07
Показать остальные 6 комментариев
18

Теговый шаблон

Просто поместите обе функции в ваш файл functions.php. Затем настройте первую функцию и добавьте имя вашей пользовательской таблицы. Далее вам потребуется метод проб и ошибок, чтобы исключить ID текущего пользователя из результирующего массива (см. комментарий).

/**
 * Получить "Лидеров" текущего пользователя
 * @param int $user_id ID текущего пользователя
 * @return array $query Массив лидеров
 */
function wpse50305_get_leaders( $user_id )
{
    global $wpdb;

    return $wpdb->query( $wpdb->prepare(
        "
            SELECT `leader_id`, `follower_id`
            FROM %s
                WHERE `follower_id` = %s
            ORDERBY `leader_id` ASC
        ",
        // Измените имя таблицы
        "{$wpdb->prefix}custom_table_name"
        $user_id
    ) );
}

/**
 * Получить массив постов, содержащих записи 
 * "Лидеров", на которых подписан текущий пользователь
 * @return array $posts Посты от текущих "Лидеров"
 */
function wpse50305_list_posts_by_leader()
{
    get_currentuserinfo();
    global $current_user;

    $user_id = $current_user->ID;

    $leaders = wpse5035_get_leaders( $user_id );
    // возможно, вам потребуется пройтись по массиву $leaders
    // и исключить ID подписчиков

    return get_posts( array(
        'author' => implode( ",", $leaders )
    ) );
}

Внутри шаблона

Здесь вы можете делать с результатами все, что угодно.

foreach ( wpse50305_list_posts_by_leader() as $post )
{
    // делаем что-то с $post
}

ПРИМЕЧАНИЕ У нас нет тестовых данных и т.д., поэтому приведенный выше код является в некоторой степени предположением. Убедитесь, что вы отредактируете этот ответ, указав, что сработало у вас, чтобы у нас был удовлетворительный результат для будущих читателей. Я одобрю правку, если у вас недостаточно репутации. Затем вы также можете удалить это примечание. Спасибо.

26 апр. 2012 г. 16:28:29
Комментарии

Спасибо за предоставленное решение. Однако у этого подхода есть недостатки с точки зрения масштабируемости. Проблема в получении id лидеров в виде массива. Этот массив может стать чрезвычайно большим в зависимости от количества пользователей, на которых подписан пользователь. Я видел, как это потребляет большое количество памяти. Это нужно делать одним SQL-запросом, который может включать соединение (JOIN) пользовательской таблицы и таблицы WP posts с последующей выборкой результатов. Лучшее решение — расширить WP_Query. Проблема в том, что я не знаю, как это сделать. Еще раз спасибо за попытку. Отзывы приветствуются.

John John
26 апр. 2012 г. 16:48:54

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

kaiser kaiser
26 апр. 2012 г. 16:51:39

WP_Query сам по себе использует JOIN между таблицей posts и postmeta при запросах. Я видел, как использование памяти PHP увеличивалось до 70–200 МБ за одну загрузку страницы. Запуск чего-то подобного с большим количеством одновременных пользователей потребовал бы экстремальной инфраструктуры. Я предполагаю, что, поскольку WordPress уже реализует подобную технику, JOIN должны быть менее затратными по сравнению с работой с массивом ID.

John John
26 апр. 2012 г. 17:00:33

Нет, извините, но нет. Также есть попытки избавиться от дополнительных JOIN. Пример: WordPress добавляет JOIN таблицы терминов при запросе нескольких таксономий, что снижает производительность. Просто используйте timer_start() перед запросом и timer_stop() после запроса, чтобы измерить разницу.

kaiser kaiser
26 апр. 2012 г. 17:04:32

Плюс: Убедитесь, что у вас есть INDEX на нужной колонке. Это дает огромную разницу в производительности. В одном из моих проектов, где нужно было выполнять запросы по широте/долготе, я создал дополнительную таблицу — небольшую, хорошо индексированную, и она работает гораздо быстрее, чем любой JOIN, который я пробовал. Для более глубокого понимания работы JOIN, прочтите это.

kaiser kaiser
26 апр. 2012 г. 17:07:20

Ах да, это также зависит от того, какой именно JOIN вы используете...

kaiser kaiser
26 апр. 2012 г. 17:09:12

Значит, ваш совет — использовать массив вместо SQL-запроса с JOIN, верно?

John John
26 апр. 2012 г. 17:13:46

Да, но, как сказал @m0r7if3r: Измерьте. Я дал вам ссылки. Вам просто нужно замерить время и показать нам, что у вас получилось. Если вы этого не сделаете, то мы просто хорошо пофантазировали :)

kaiser kaiser
26 апр. 2012 г. 17:32:57

Я обязательно попробую оба решения. Но для замера времени мне нужно будет наполнить базу тестовыми данными, что займет несколько часов (добавлю в БД 50к пользователей). Сделаю и отпишусь.

John John
26 апр. 2012 г. 17:53:28

@John рад это слышать. Очень хочется узнать результаты.

kaiser kaiser
26 апр. 2012 г. 17:57:06

Ок, вот результаты тестов. Я добавил около 47K пользователей из CSV-файла. Затем запустил цикл for, чтобы первые 45 пользователей подписались на всех остальных. В результате в моей кастомной таблице оказалось 3,704,951 записей. Изначально решение @m0r7if3r давало время запроса 95 секунд, которое сократилось до 0.020 мс после включения индексации по столбцу leader_id. Общее потребление памяти PHP составило около 20MB. С другой стороны, ваше решение занимало от 2 до 5 секунд на запрос при включенной индексации. Общее потребление памяти PHP составило около 117MB.

John John
27 апр. 2012 г. 16:32:02

@John Не мог бы ты добавить это как отдельный ответ? Очень рад, что ты провел этот тест и предоставил нам реальные данные. :) Что я бы оценил еще больше - это более "реалистичный" тест: пусть каждый пользователь подписывается на $leader_amount = rand( 0, 5 ); а затем добавляет число $leader_amount умножить на $random_ids = rand( 0, 47000 ); для каждого пользователя. Пока что мы знаем следующее: мое решение было бы крайне плохим, если пользователь подписан на каждого другого пользователя. Кроме того: тебе нужно показать как именно ты проводил тест и где именно добавлял таймеры. Это одноразовый вопрос, и мне он очень нравится :)

kaiser kaiser
27 апр. 2012 г. 19:03:43

@John Я поделился твоим вопросом и участием и хотел отметить, что всем очень нравится твоя работа над этим (смотри апвоты). :)

kaiser kaiser
27 апр. 2012 г. 19:14:44

Я проведу дополнительные тесты, основываясь на твоем предложении провести более реалистичное тестирование, и затем напишу ответ с деталями. Однако для настоящего реалистичного теста есть еще две переменные, которые я пока не могу смоделировать. Первая: как минимум 20 постов от каждого пользователя. Сейчас я тестировал только с около 50 постами от 2-3 пользователей. Вторая: высокая посещаемость. Эти две переменные также повлияют на результаты в реальных условиях.

John John
27 апр. 2012 г. 21:22:28

Дальше изучал твое предложение рандомизировать следующее. Однако я не смог разобраться в фрагментах кода, которые ты написал. Полный сниппет был бы более полезным. Вот что я сделал, чтобы первые 50 пользователей подписались на всех остальных: for ($j = 2; $j <= 52; $j++) { for ($i = ($j + 1); $i <= 47000; $i++) { $rows_affected = $wpdb->insert($table_name, array( 'leader_id' => $i, 'follower_id' => $j)); } }. Очень простой вложенный цикл for.

John John
27 апр. 2012 г. 21:52:29

О трафике) Вы могли бы использовать последние версии Apache, Nginx, минификацию, кеширование, сжатие и т.д. Каждый из этих факторов повлиял бы на результат. Но нам нужны результаты в локальной, "чистой" среде (без плагинов, стандартная тема), которые можно действительно объективно оценить.

kaiser kaiser
27 апр. 2012 г. 22:03:27

Вы получили уведомление о моем комментарии по поводу рандомизации? Кажется, я забыл упомянуть ваш ник в комментарии. Тем не менее, думаю, владелец поста получил уведомление. Подожду ваших замечаний перед тем как продолжить.

John John
27 апр. 2012 г. 22:33:28

Я добавлю еще один ответ (мы можем его обработать & модифицировать/отредактировать), потому что форматирование кода в комментариях просто ужасно :P

kaiser kaiser
28 апр. 2012 г. 12:11:12
Показать остальные 13 комментариев
1

Примечание: Этот ответ приведен здесь, чтобы избежать длительного обсуждения в комментариях

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

    for ( $j = 2; $j <= 52; $j++ ) 
    {
        for ( $i = ($j + 1); $i <= 47000; $i++ )
        {
            $rows_affected = $wpdb->insert( $table_name, array( 'leader_id' => $i, 'follower_id' => $j ) );
        }
    }
    

    Автор о тесте Для этого я добавил около 47K пользователей из CSV-файла. Затем запустил цикл, чтобы первые 45 пользователей подписались на всех остальных.

    • В результате в моей кастомной таблице оказалось 3,704,951 записей.
    • Изначально решение @m0r7if3r давало время запроса 95 секунд, которое сократилось до 0.020 мс после включения индексации для колонки leader_id. Общий расход памяти PHP составил около 20MB.
    • С другой стороны, ваше решение заняло от 2 до 5 секунд для запроса с включенной индексацией. Общий расход памяти PHP составил около 117MB.
  2. Мой ответ на этот ↑ тест:

    более "реалистичный" тест: пусть каждый пользователь подписывается на $leader_amount = rand( 0, 5 ); лидеров, а затем добавляется количество $leader_amount x $random_ids = rand( 0, 47000 ); для каждого пользователя. Пока что мы знаем: мое решение было бы крайне плохим, если пользователь подписан на всех остальных. Кроме того: вам нужно показать, как именно вы проводили тест и где именно добавляли таймеры.

    Также должен отметить, что ↑ приведенное измерение времени нельзя считать точным, так как оно включает время выполнения цикла. Лучше было бы пройтись по результирующему набору ID во втором цикле.

дальнейший процесс здесь

28 апр. 2012 г. 12:14:37
Комментарии

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

John John
30 апр. 2012 г. 09:47:23