notes 笔记notes 笔记
Home
Article
Category
Tag
Timeline
Home
Article
Category
Tag
Timeline
  • build

    • 环境变量
  • browse

    • 2fa
    • sse
    • token
  • database

    • mongodb

      • start
    • mysql

      • curd
      • 安装
      • join
      • 多对多
      • 性能优化
      • 表设计
      • 常见问题
  • docker

    • github-actions

      • local
      • prod
    • docker-compose
    • index
    • podman
  • file

    • 文件下载
    • 原生 node.js下载文件
  • git

    • index
    • multiple-github-accounts
    • auto-commit
    • pat
    • rebase
  • linux

    • grep
    • index
    • ssh
    • vim
    • windows
  • network

    • best-proxy-way
    • git-via-https
    • github-push-fail
  • nginx

    • acme.sh
    • cache
    • https
    • index
    • safe
  • node

    • fnm
    • tool
  • obsidian

    • ish
    • start
  • react

    • index
    • set-state
  • summary

    • index
  • vue

    • index
    • typescript
  • libs
  • open-source
  • 在 MySQL 中处理 role 表与 resource 表的多对多关系查询方案
    • 方案 1:直接 JOIN 关联(推荐)
    • 方案 2:先查角色 IDs,再用 IN 关联(特定场景适用)
    • 方案 3:子查询关联(避免应用层多次交互)
    • 性能优化建议
    • 总结

在 MySQL 中处理 role 表与 resource 表的多对多关系查询方案

取决于你的查询需求(如是否需要过滤角色、数据量大小、性能要求等)。以下是几种常见的查询方案及适用场景分析:

方案 1:直接 JOIN 关联(推荐)

如果需要一次性获取角色及其关联的资源(例如查询所有角色并附带它们的资源列表),最直接的方式是通过中间表 role_resource 进行 JOIN,避免多次查询数据库。

适用场景:

  • 需要一次性获取角色及其关联资源的完整数据(减少应用层与数据库的交互次数)。
  • 角色数量较少或无复杂过滤条件(如不需要先筛选符合条件的角色)。

示例 SQL: 假设中间表为 role_resource(包含 role_id 和 resource_id),resource 表包含 id、name 等字段:

SELECT 
  r.*, 
  GROUP_CONCAT(res.name SEPARATOR ', ') AS resource_names  -- 合并资源名称(可选)
FROM 
  role r
LEFT JOIN 
  role_resource rr ON r.id = rr.role_id
LEFT JOIN 
  resource res ON rr.resource_id = res.id
GROUP BY 
  r.id;  -- 按角色分组,避免资源重复

说明:

  • 使用 LEFT JOIN 确保即使角色没有关联资源也能被查询到(若只需有资源的角色,改用 INNER JOIN)。
  • GROUP_CONCAT 可将同一角色的多个资源名称合并为一个字段(根据业务需求可选)。
  • 若需筛选特定角色(如 r.name = '管理员'),直接在 WHERE 子句中添加条件即可:
SELECT ... 
FROM role r
LEFT JOIN role_resource rr ON r.id = rr.role_id
LEFT JOIN resource res ON rr.resource_id = res.id
WHERE r.name = '管理员'  -- 直接过滤角色
GROUP BY r.id;

方案 2:先查角色 IDs,再用 IN 关联(特定场景适用)

如果需要先根据某些条件筛选角色,再查询这些角色关联的资源(例如前端传入角色名称模糊查询,再获取这些角色的资源),可以先查询符合条件的 roleIds,再用 IN 关联查询资源。

适用场景:

  • 角色需要先经过复杂条件过滤(如模糊查询、分页等),无法直接通过 JOIN 一次性完成。
  • 角色数量较多,但 roleIds 结果集较小(避免 IN 子句过长导致性能下降)。

示例步骤:

  1. 第一步:查询符合条件的角色 IDs
SELECT id FROM role 
WHERE name LIKE '%管理员%'  -- 示例:模糊查询角色名称
LIMIT 10;  -- 分页限制数量

假设返回的 roleIds 为 [1, 3, 5]。

  1. 第二步:用 IN 关联查询资源
SELECT 
  r.id AS role_id,
  r.name AS role_name,
  res.id AS resource_id,
  res.name AS resource_name
FROM 
  role r
INNER JOIN 
  role_resource rr ON r.id = rr.role_id
INNER JOIN 
  resource res ON rr.resource_id = res.id
WHERE 
  r.id IN (1, 3, 5);  -- 使用第一步得到的 roleIds

说明:

  • 这种方式分两步查询,但可以灵活处理角色的复杂过滤逻辑(如分页、多条件组合)。
  • 若 roleIds 结果集很大(如超过 1000 个),IN 子句的性能会下降(MySQL 对 IN 的优化在大结果集时不如 JOIN),此时建议改用 JOIN 直接关联。

方案 3:子查询关联(避免应用层多次交互)

如果不想在应用层处理两次查询(先查角色 IDs,再查资源),可以用子查询将两步合并为一条 SQL,避免应用层与数据库的多次交互。

适用场景:

  • 角色需要先过滤,但希望避免应用层处理两次查询(如减少代码复杂度)。

示例 SQL:

SELECT 
  r.id AS role_id,
  r.name AS role_name,
  res.id AS resource_id,
  res.name AS resource_name
FROM 
  role r
INNER JOIN 
  role_resource rr ON r.id = rr.role_id
INNER JOIN 
  resource res ON rr.resource_id = res.id
WHERE 
  r.id IN (
    SELECT id FROM role 
    WHERE name LIKE '%管理员%'  -- 子查询过滤角色
    LIMIT 10
  );

说明:

  • 子查询的结果会被优化为临时表,MySQL 会自动处理其执行计划(性能与直接 JOIN 接近)。
  • 对于复杂子查询(如多层嵌套),需注意索引优化(见下文“性能优化建议”)。

性能优化建议

无论选择哪种方案,都需要注意以下索引优化,以提升查询效率:

  1. 主表索引:
  • role.id(主键,必须存在)。
  • resource.id(主键,必须存在)。
  1. 中间表索引:
  • role_resource.role_id(外键,关联 role.id,需添加索引)。
  • role_resource.resource_id(外键,关联 resource.id,需添加索引)。
  • 若经常通过 role_id 或 resource_id 反向查询,可添加复合索引(如 (role_id, resource_id))。
  1. 避免大结果集 IN 查询:

当 roleIds 结果集超过 1000 个时,IN 子句的性能会显著下降(MySQL 对 IN 的解析效率随结果集增大而降低)。此时建议改用 JOIN 直接关联中间表和角色表。

总结

是否需要先查 roleIds 再用 IN 关联,取决于具体业务场景:

  • 推荐直接 JOIN:如果需要一次性获取角色及其关联资源,或角色过滤条件简单(可直接通过 JOIN 条件实现)。
  • 考虑 IN 或子查询:如果角色需要先经过复杂过滤(如分页、模糊查询),或希望避免应用层多次交互。

无论哪种方式,索引优化是关键(尤其是中间表的外键索引),能有效提升多对多关联查询的性能。

最近更新:: 2025/10/19 23:44
Contributors: qyhever
Prev
join
Next
性能优化