WordPress MySQL BuddyPress 成员类型的查询优化

WordPress MySQL query optimization for the BuddyPress member types

我正在开发一款已经拥有超过 10000000 名用户的自定义应用程序。

我必须创建一个从特定成员类型中提取用户的自定义查询。

问题是,如果我只想查看 20 个用户并进行分页,则需要大量时间从数据库中取出数据,并且经常会出现严重错误,因为查询非常繁重。

如果我使用 BP_User_Query (Buddypress class),也会发生同样的情况,因为我的代码是基于它的。

最大的问题是Buddypressmember_type已经被记录在WP术语中,并且通过复杂的关系到达。

这是我的查询:

SELECT 
    wp_users.*,
    t_ex.name AS member_type,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='first_name' AND wp_usermeta.user_id=wp_users.ID ) AS first_name,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='last_name' AND wp_usermeta.user_id=wp_users.ID ) AS last_name,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='nickname' AND wp_usermeta.user_id=wp_users.ID ) AS nickname,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='description' AND wp_usermeta.user_id=wp_users.ID ) AS description,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='last_update' AND wp_usermeta.user_id=wp_users.ID ) AS last_update,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='wp_capabilities' AND wp_usermeta.user_id=wp_users.ID ) AS caps,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='rich_editing' AND wp_usermeta.user_id=wp_users.ID ) AS rich_editing
FROM
    wp_users
    LEFT JOIN wp_term_relationships tr_ex ON tr_ex.object_id = wp_users.ID
    LEFT JOIN wp_term_taxonomy tt_ex ON tt_ex.term_taxonomy_id = tr_ex.term_taxonomy_id
    LEFT JOIN wp_terms t_ex ON t_ex.term_id = tt_ex.term_id
WHERE
    1=1
AND
    tt_ex.taxonomy = 'bp_member_type'
AND
    t_ex.name = 'platformuser'
GROUP BY wp_users.ID
ORDER BY wp_users.display_name ASC
LIMIT 0, 20

有没有更好的解决方案,如何在不对数据库造成如此困难的情况下获得所需的结果。

我要查找的参数位于t_ex.name,在我的例子中是'platformuser'

我认为问题出在分组和顺序上:

...

GROUP BY wp_users.ID
ORDER BY wp_users.display_name ASC

...

但不知道为什么。我也需要。

一些要处理的东西。

首先一千万用户对于WordPress来说是一个很大的数字。你已经知道了。

其次,请考虑重写您的查询以执行所谓的“延迟连接”。你有臭名昭著的性能反模式SELECT many_long_rows ... ORDER BY something LIMIT small_number。这迫使 MySQL 对一大堆数据进行排序,只丢弃其中的一小部分。

建议重写:从子查询开始获取您感兴趣的 wp_users.ID 值。根据您的示例,子查询是这样的。

         SELECT wp_users.ID
            FROM wp_users
            LEFT JOIN wp_term_relationships tr_ex ON tr_ex.object_id = wp_users.ID
            LEFT JOIN wp_term_taxonomy tt_ex ON tt_ex.term_taxonomy_id = tr_ex.term_taxonomy_id
            LEFT JOIN wp_terms t_ex ON t_ex.term_id = tt_ex.term_id
           WHERE 1=1
             AND tt_ex.taxonomy = 'bp_member_type'
             AND t_ex.name = 'platformuser'
           GROUP BY wp_users.ID
           ORDER BY wp_users.display_name ASC
           LIMIT 0, 20

这必须对少得多的数据进行排序...仅对 ID 值进行排序。所以它应该更快并且在 MySQL.

中占用更少的内存

然后在您的主查询中使用该子查询来处理您关心的 20 个用户。

SELECT 
    wp_users.*,
    'platformuser' AS member_type,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='first_name' AND wp_usermeta.user_id=wp_users.ID ) AS first_name,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='last_name' AND wp_usermeta.user_id=wp_users.ID ) AS last_name,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='nickname' AND wp_usermeta.user_id=wp_users.ID ) AS nickname,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='description' AND wp_usermeta.user_id=wp_users.ID ) AS description,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='last_update' AND wp_usermeta.user_id=wp_users.ID ) AS last_update,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='wp_capabilities' AND wp_usermeta.user_id=wp_users.ID ) AS caps,
    ( SELECT meta_value FROM wp_usermeta WHERE wp_usermeta.meta_key='rich_editing' AND wp_usermeta.user_id=wp_users.ID ) AS rich_editing
FROM
    wp_users
WHERE wp_users.ID IN (
         SELECT wp_users.ID
            FROM wp_users
            LEFT JOIN wp_term_relationships tr_ex ON tr_ex.object_id = wp_users.ID
            LEFT JOIN wp_term_taxonomy tt_ex ON tt_ex.term_taxonomy_id = tr_ex.term_taxonomy_id
            LEFT JOIN wp_terms t_ex ON t_ex.term_id = tt_ex.term_id
           WHERE 1=1
             AND tt_ex.taxonomy = 'bp_member_type'
             AND t_ex.name = 'platformuser'
           GROUP BY wp_users.ID
           ORDER BY wp_users.display_name ASC
           LIMIT 0, 20
      )     
GROUP BY wp_users.ID
ORDER BY wp_users.display_name ASC
LIMIT 0, 20

查找仅仅二十个用户的元数据不会花很长时间。

ThirdWordPress's wp_users table, as defined by https://ma.tt 和公司在 display_name 列上没有索引。所以 MySQL 避免巨大排序的索引技巧不起作用。您可以修复此问题

  • 通过使用 ORDER BY wp_users.user_nicename 代替 display_name,或通过
  • 像这样在 display_name 列上创建索引。
    ALTER TABLE wp_users ADD KEY display_name (display_name);
    

第四,如果你是运行旧版本的MySQL(8之前)或MariaDB(10.3之前),升级它。并将您的 table 转换为使用 InnoDB 和 DYNAMIC 行格式。您正在努力挑战一千万用户的极限。为什么在这样做时使用过时的软件?

Fifth(如果你做了上面的事情就不那么重要了)wp_usermeta 上的索引对于你所做的查找不是最佳的。这一系列 MySQL 语句将重新索引 table 以提供更快的查找速度。这可能需要一段时间。

ALTER TABLE wp_usermeta ADD UNIQUE KEY umeta_id (umeta_id);
ALTER TABLE wp_usermeta DROP PRIMARY KEY;
ALTER TABLE wp_usermeta ADD PRIMARY KEY (user_id, meta_key, umeta_id);
ALTER TABLE wp_usermeta DROP KEY user_id;
ALTER TABLE wp_usermeta DROP KEY meta_key;
ALTER TABLE wp_usermeta ADD KEY meta_key (meta_key, user_id);

@RickJames 和我发布了一个(免费开源)WordPress plugin to handle the InnoDB conversion and reindexing for you, or you can certainly do it yourself. You can use it with WP-CLI 来避免超时。

我认为我们插件的下一个版本也应该将 display_name 索引添加到 wp_users 以涵盖您的情况。 (我听说其他人也有类似的问题。)

OJones 所说的,加上

为了避免排序:

       GROUP BY wp_users.ID
       ORDER BY wp_users.display_name ASC

-->

       GROUP BY wp_users.display_name ,    wp_users.ID
       ORDER BY wp_users.display_name ASC, wp_users.ID ASC

(通过使这两行基本相等,可以避免额外的排序。)