MySQL:如何select和处理"n to n"相关数据以提高性能
MySQL: How to select and process "n to n" related data with a view to performance
分配:
我需要 select 具有多对多关系的数据并搜索具有良好性能的解决方案。我目前有两个可行的解决方案(见下文)。
示例/说明:
比赛是由组织推动的。一场比赛可以有none个组织作为发起人。我只需要每场比赛在结果中出现一次,并提供发起人的 ID 列表。
数据结构:
- Table "competition": (id, name)
- Table "organisation": (id, name)
- Table "competition_organisation": (competitionID, organisationID)
所需结果:
|id|名称|promoter_list|
|1|欧洲冠军联赛|1241|
|2|国际足联世界杯|1240|
|3|FIFA 世界杯预赛 - 非洲|1240, 1242|
开发平台: Cold Fusion
数据库: MySQL
基于给定答案的附加说明:
- 我的问题的主要目的是找到一种比我过去更好地处理这种关系的方法。比赛只是我需要的例子之一。
- 我试着让它更简单,也许我忽略了一个事实。在我的应用程序中,我还需要组织名称。为此,我加入了组织table。
- 比赛有比我在这个例子中描述的更多的相关信息。我的应用程序中的查询使用多个连接到其他 tables.
方案一:
- 查询到select比赛数据
- 循环结果
- 将每条记录存储在循环内的数组中
- 附加查询 select 结果/循环中每条记录的启动子
- 将带有另一个查询循环的启动子 ID 添加到数组
主查询:
SELECT competition.id, competition.name
FROM competition
WHERE ...
循环内附加启动子查询:
SELECT DISTINCT organisation.id
FROM organisation
INNER JOIN competition_organisation
ON competition_organisation.organisationID = organisation.id
WHERE competition_organisation.competitionID = competition.id[currentrow]#
方案二:
- 仅使用一个子查询 select
- 循环结果
- 将每条记录存储在循环内的数组中
SELECT competition.id, competition.name,
(
SELECT CONVERT(GROUP_CONCAT(organisation.id SEPARATOR ', ') USING utf8)
FROM organisation
WHERE organisation.id in
(
SELECT DISTINCT competition_organisation.organisationID
FROM competition_organisation
WHERE competition_organisation.competitionID = competition.id
)
) AS promoter_list
FROM competition
WHERE ...
方案三(Spencer7593提出):
SELECT c.id,
c.name,
CONVERT(GROUP_CONCAT(DISTINCT o.id ORDER BY o.id) USING utf8) AS promoter_id_list,
CONVERT(GROUP_CONCAT(DISTINCT o.name ORDER BY o.id) USING utf8) AS promoter_list
FROM competition c
LEFT JOIN competition_organisation c_o ON c_o.competitionID = c.id
LEFT JOIN organisation o ON o.id = c_o.organisationID
GROUP BY c.id, c.name
(我稍微修改了代码并添加了组织名称)
方案四(Thorsten Kettner 提出,Rick James 优化):
SELECT id, name,
( SELECT CONVERT(GROUP_CONCAT(organisationID SEPARATOR ', ') USING utf8)
FROM competition_organisation
WHERE competitionID = c.id
) AS promoter_id_list,
( SELECT CONVERT(GROUP_CONCAT(organisation.name SEPARATOR ', ') USING utf8)
FROM competition
left join competition_organisation on competition_organisation.competitionID = competition.id
left join organisationen on organisationen.id = competition_organisation.organisationID
WHERE competitionID = c.id
) AS promoter_list
FROM competition AS c
(还添加了组织名称,希望以正确的方式)
性能比较:
解决方案 1 - 100 条记录:~30ms + (100 x ~1ms) = ~130ms
解决方案 1 - 1000 条记录:~70ms + (1000 x ~1ms) = ~1070ms
解决方案 2 - 100 条记录:~5500 毫秒
解决方案 2 - 1000 条记录:~48000ms
解决方案 3 - 100 条记录:~120 毫秒
解决方案 3 - 1000 条记录:~210ms
解决方案 4 - 100 条记录:~110 毫秒
解决方案 4 - 1000 条记录:~200ms
如您所见,解决方案2的性能很糟糕。
- 是否有优化解决方案 2 查询以显着提高性能的选项?
- 是否有我没有想到的替代解决方案?
- 还是我应该留在解决方案 1?
结论:
我决定采用 Spencer 的解决方案 3。 3和4的性能几乎相同。但是 3 的代码更简单并且与我现有的查询完美匹配,尤其是与他们的左连接。
我对结果很满意。性能大大提高,以后我需要的代码/文件更少。
非常感谢您的帮助!
单个查询几乎总是比过程更快。如果不是,这可能表示查询中存在缺陷。
DISTINCT
不属于 IN
子查询。应该由 DBMS 如何 来查找数据。 IN
子句也应该是不相关的。如果您想要或需要相关的东西,请改用 EXISTS
。那么,你为什么要加入tableorganisation
呢? table.
你不需要任何东西
select c.id, c.name, co.promoter_list
from competition c
left join
(
select
competitionid,
group_concat(organisationid separator ', ') as promoter_list
from competition_organisation
group by competitionid
) co on co.competitionid = c.id;
解决方案 3:
利用外连接操作和 MySQL 特定的 GROUP_CONCAT
聚合函数到 return 逗号分隔的 organizationid 值列表。
-- SHOW VARIABLES LIKE 'group_concat_max_len';
-- SET group_concat_max_len = 1048576;
SELECT c.id AS id
, c.name AS name
, GROUP_CONCAT(DISTINCT p.organisationid ORDER BY p.organisationid) AS promoter_list
FROM competition c
LEFT
JOIN competition_organisation p
ON p.competitionid = c.id
GROUP
BY c.id
, c.name
ORDER
BY c.id
, c.name
请注意,如果 GROUP_CONCAT
生成的字符串长度超过 group_concat_max_len
,该字符串将自动截断为允许的长度。 (没有错误,没有警告)。
将returned字符串的字节长度与系统变量的值进行比较,检测字符串是否被截断。
organisation
table 也可以包含在查询中,如果有必要或有充分的理由这样做的话。
SELECT c.id AS id
, c.name AS name
, GROUP_CONCAT(DISTINCT o.id ORDER BY o.id) AS promoter_list
FROM competition c
LEFT
JOIN competition_organisation p
ON p.competitionid = c.id
LEFT
JOIN organisation o
ON o.id = p.organisationid
GROUP
BY c.id
, c.name
ORDER
BY c.id
, c.name
(注意:此答案包含两种改进 Thorsten 答案的方法,但我尚未验证它是否是 OP 问题的有效解决方案。)
方案A:
LEFT JOIN ( SELECT ... )
很可能表现不佳。
SELECT id,
name,
( SELECT GROUP_CONCAT(organisationid SEPARATOR ', ')
FROM competition_organization
WHERE competitionid = c.id
) AS promoter_list
FROM competition AS c;
将LEFT
变成关联子查询,当什么都没有时returnNULL
(或''
?)
B计划:
如果不需要 LEFT
——也就是说,如果总是有一个组织列表,那么这个重写可能会更好:
SELECT c.id, c.name, co.promoter_list
FROM ( SELECT competitionid,
GROUP_CONCAT(organisationid SEPARATOR ', ') AS promoter_list
FROM competition_organization
GROUP BY competitionid
) AS co
JOIN competition AS c ON c.id = co.competitionid;
这样做的好处是可以完全构建 GROUP_CONCATs
、then 之后的 id
和 name
。
假设 c
:PRIMARY KEY(id)
。
两种变体都假定 co
:INDEX(competitionid, organizationid)
的顺序。
参见 http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table 构建最优的多对多 table。
分配:
我需要 select 具有多对多关系的数据并搜索具有良好性能的解决方案。我目前有两个可行的解决方案(见下文)。
示例/说明:
比赛是由组织推动的。一场比赛可以有none个组织作为发起人。我只需要每场比赛在结果中出现一次,并提供发起人的 ID 列表。
数据结构:
- Table "competition": (id, name)
- Table "organisation": (id, name)
- Table "competition_organisation": (competitionID, organisationID)
所需结果:
|id|名称|promoter_list|
|1|欧洲冠军联赛|1241|
|2|国际足联世界杯|1240|
|3|FIFA 世界杯预赛 - 非洲|1240, 1242|
开发平台: Cold Fusion
数据库: MySQL
基于给定答案的附加说明:
- 我的问题的主要目的是找到一种比我过去更好地处理这种关系的方法。比赛只是我需要的例子之一。
- 我试着让它更简单,也许我忽略了一个事实。在我的应用程序中,我还需要组织名称。为此,我加入了组织table。
- 比赛有比我在这个例子中描述的更多的相关信息。我的应用程序中的查询使用多个连接到其他 tables.
方案一:
- 查询到select比赛数据
- 循环结果
- 将每条记录存储在循环内的数组中
- 附加查询 select 结果/循环中每条记录的启动子
- 将带有另一个查询循环的启动子 ID 添加到数组
主查询:
SELECT competition.id, competition.name
FROM competition
WHERE ...
循环内附加启动子查询:
SELECT DISTINCT organisation.id
FROM organisation
INNER JOIN competition_organisation
ON competition_organisation.organisationID = organisation.id
WHERE competition_organisation.competitionID = competition.id[currentrow]#
方案二:
- 仅使用一个子查询 select
- 循环结果
- 将每条记录存储在循环内的数组中
SELECT competition.id, competition.name,
(
SELECT CONVERT(GROUP_CONCAT(organisation.id SEPARATOR ', ') USING utf8)
FROM organisation
WHERE organisation.id in
(
SELECT DISTINCT competition_organisation.organisationID
FROM competition_organisation
WHERE competition_organisation.competitionID = competition.id
)
) AS promoter_list
FROM competition
WHERE ...
方案三(Spencer7593提出):
SELECT c.id,
c.name,
CONVERT(GROUP_CONCAT(DISTINCT o.id ORDER BY o.id) USING utf8) AS promoter_id_list,
CONVERT(GROUP_CONCAT(DISTINCT o.name ORDER BY o.id) USING utf8) AS promoter_list
FROM competition c
LEFT JOIN competition_organisation c_o ON c_o.competitionID = c.id
LEFT JOIN organisation o ON o.id = c_o.organisationID
GROUP BY c.id, c.name
(我稍微修改了代码并添加了组织名称)
方案四(Thorsten Kettner 提出,Rick James 优化):
SELECT id, name,
( SELECT CONVERT(GROUP_CONCAT(organisationID SEPARATOR ', ') USING utf8)
FROM competition_organisation
WHERE competitionID = c.id
) AS promoter_id_list,
( SELECT CONVERT(GROUP_CONCAT(organisation.name SEPARATOR ', ') USING utf8)
FROM competition
left join competition_organisation on competition_organisation.competitionID = competition.id
left join organisationen on organisationen.id = competition_organisation.organisationID
WHERE competitionID = c.id
) AS promoter_list
FROM competition AS c
(还添加了组织名称,希望以正确的方式)
性能比较:
解决方案 1 - 100 条记录:~30ms + (100 x ~1ms) = ~130ms
解决方案 1 - 1000 条记录:~70ms + (1000 x ~1ms) = ~1070ms
解决方案 2 - 100 条记录:~5500 毫秒
解决方案 2 - 1000 条记录:~48000ms
解决方案 3 - 100 条记录:~120 毫秒
解决方案 3 - 1000 条记录:~210ms
解决方案 4 - 100 条记录:~110 毫秒
解决方案 4 - 1000 条记录:~200ms
如您所见,解决方案2的性能很糟糕。
- 是否有优化解决方案 2 查询以显着提高性能的选项?
- 是否有我没有想到的替代解决方案?
- 还是我应该留在解决方案 1?
结论:
我决定采用 Spencer 的解决方案 3。 3和4的性能几乎相同。但是 3 的代码更简单并且与我现有的查询完美匹配,尤其是与他们的左连接。
我对结果很满意。性能大大提高,以后我需要的代码/文件更少。
非常感谢您的帮助!
单个查询几乎总是比过程更快。如果不是,这可能表示查询中存在缺陷。
DISTINCT
不属于 IN
子查询。应该由 DBMS 如何 来查找数据。 IN
子句也应该是不相关的。如果您想要或需要相关的东西,请改用 EXISTS
。那么,你为什么要加入tableorganisation
呢? table.
select c.id, c.name, co.promoter_list
from competition c
left join
(
select
competitionid,
group_concat(organisationid separator ', ') as promoter_list
from competition_organisation
group by competitionid
) co on co.competitionid = c.id;
解决方案 3:
利用外连接操作和 MySQL 特定的 GROUP_CONCAT
聚合函数到 return 逗号分隔的 organizationid 值列表。
-- SHOW VARIABLES LIKE 'group_concat_max_len';
-- SET group_concat_max_len = 1048576;
SELECT c.id AS id
, c.name AS name
, GROUP_CONCAT(DISTINCT p.organisationid ORDER BY p.organisationid) AS promoter_list
FROM competition c
LEFT
JOIN competition_organisation p
ON p.competitionid = c.id
GROUP
BY c.id
, c.name
ORDER
BY c.id
, c.name
请注意,如果 GROUP_CONCAT
生成的字符串长度超过 group_concat_max_len
,该字符串将自动截断为允许的长度。 (没有错误,没有警告)。
将returned字符串的字节长度与系统变量的值进行比较,检测字符串是否被截断。
organisation
table 也可以包含在查询中,如果有必要或有充分的理由这样做的话。
SELECT c.id AS id
, c.name AS name
, GROUP_CONCAT(DISTINCT o.id ORDER BY o.id) AS promoter_list
FROM competition c
LEFT
JOIN competition_organisation p
ON p.competitionid = c.id
LEFT
JOIN organisation o
ON o.id = p.organisationid
GROUP
BY c.id
, c.name
ORDER
BY c.id
, c.name
(注意:此答案包含两种改进 Thorsten 答案的方法,但我尚未验证它是否是 OP 问题的有效解决方案。)
方案A:
LEFT JOIN ( SELECT ... )
很可能表现不佳。
SELECT id,
name,
( SELECT GROUP_CONCAT(organisationid SEPARATOR ', ')
FROM competition_organization
WHERE competitionid = c.id
) AS promoter_list
FROM competition AS c;
将LEFT
变成关联子查询,当什么都没有时returnNULL
(或''
?)
B计划:
如果不需要 LEFT
——也就是说,如果总是有一个组织列表,那么这个重写可能会更好:
SELECT c.id, c.name, co.promoter_list
FROM ( SELECT competitionid,
GROUP_CONCAT(organisationid SEPARATOR ', ') AS promoter_list
FROM competition_organization
GROUP BY competitionid
) AS co
JOIN competition AS c ON c.id = co.competitionid;
这样做的好处是可以完全构建 GROUP_CONCATs
、then 之后的 id
和 name
。
假设 c
:PRIMARY KEY(id)
。
两种变体都假定 co
:INDEX(competitionid, organizationid)
的顺序。
参见 http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table 构建最优的多对多 table。