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
查找仅仅二十个用户的元数据不会花很长时间。
Third、WordPress'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
(通过使这两行基本相等,可以避免额外的排序。)
我正在开发一款已经拥有超过 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
查找仅仅二十个用户的元数据不会花很长时间。
Third、WordPress'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
(通过使这两行基本相等,可以避免额外的排序。)