优化加入 MySQL 或卸载到应用层

Optimal joins in MySQL or offloading to application layer

我在 MySQL 数据库中有 3 个 table:coursesusersparticipants,其中包含大约 3000 万、30k 和 3k 条目分别。

我的目标是(有效地)计算出已分配到符合我们标准的课程的用户数量。标准有点复杂,但对于这个例子,我们只关心 deleted_atnull 的用户和 deleted_atnullactive 是的课程1.

简化了这些列:

users:

id deleted_at
1 null
2 2022-01-01

courses:

id active  deleted_at
1 1 null
1 1 2020-01-01
2 0 2020-01-01

participants:

id participant_id  course_id
1 1 1
2 1 2
3 2 2

根据上面的数据,我们将得到的数字是 1,因为只有用户 1 没有被删除,并且该用户分配给了 some处于活动状态且未删除的课程 (id 1)。

这是我尝试过的列表。

一个查询示例是:

SELECT COUNT(DISTINCT participant_id)
FROM `participants`
INNER JOIN
  (SELECT `courses`.`id`
   FROM `courses`
   WHERE (`active` = '1')
     AND `deleted_at` IS NULL) AS `tempCourses` ON `tempCourses`.`id` = `participants`.`course_id`
WHERE `participant_type` = 'Eloomi\Models\User'
  AND `participant_id` in
    (SELECT `users`.`id`
     FROM `users`
     WHERE `users`.`deleted_at` IS NULL)

据我所知,这样做会创建一个巨大的 table,然后才会开始应用 where。在我看来,应该可以短路很多,因为一旦我们为用户找到匹配项,我们就可以忽略它的发展。在我看来,这就是在应用程序层中如何处理它。

我们可以在应用层基于每个用户执行此操作,但对数据库的请求数量会使此解决方案变得糟糕。

我将它标记为 PHP 以及 MySQL,不是因为它必须是 PHP,而是因为我不介意将某些部分卸载到应用程序层,如果那样的话是必须的。根据我的经验,连接并不总是以最佳方式使用索引

编辑: 具体说明我的问题:有人可以帮我提供一种有效的方法来提取已分配给活动的未删除课程的未删除用户的数量吗?

我会这样写:

SELECT COUNT(DISTINCT p.participant_id)
FROM courses AS c 
INNER JOIN participants AS p
 ON c.id = p.course_id
INNER JOIN users AS u 
 ON p.participant_id = u.id
WHERE u.deleted_at IS NULL
 AND c.active = 1 AND c.deleted_at IS NULL
 AND p.participant_type = 'Eloomi\Models\User';

MySQL 可能会以其他顺序加入 table,而不是您在查询中列出 table 的顺序。

我希望courses是第一个table MySQL访问,因为它可能是最小的table。特别是在 activedeleted_at 过滤之后。以下索引将有助于缩小过滤范围,因此只检查匹配的行:

ALTER TABLE courses ADD KEY (active, deleted_at);

每个索引都隐式地将 table 的主键(例如 id)附加为最后一列。该列是索引的一部分,用于连接 participants。因此,您需要 participants 中的索引,连接使用该索引在 table 中查找相应的行。索引中列的顺序很重要。

ALTER TABLE participants ADD KEY (course_id, participant_type, participant_id);

participant_id用于加入userstable。 MySQL 的优化器可能更喜欢通过其主键连接到 users,但您还想通过 deleted_at 对其进行限制,因此您可能需要此索引:

ALTER TABLE users ADD KEY (id, deleted_at);

并且您可能需要使用索引提示来引导优化器优先使用此二级索引而不是主键索引。

SELECT COUNT(DISTINCT p.participant_id)
FROM courses AS c 
INNER JOIN participants AS p
 ON c.id = p.course_id
INNER JOIN users AS u USE INDEX(deleted_at)
 ON p.participant_id = u.id
WHERE u.deleted_at IS NULL
 AND c.active = 1 AND c.deleted_at IS NULL
 AND p.participant_type = 'Eloomi\Models\User';

MySQL 知道如何使用复合索引,即使某些条件在 join 子句中而其他条件在 WHERE 子句中。

警告:我还没有测试过这个。选择索引可能需要多次尝试,并在每次尝试后测试 EXPLAIN。