MySQL JOIN LIMIT 动态查询优化

Posted

技术标签:

【中文标题】MySQL JOIN LIMIT 动态查询优化【英文标题】:MySQL JOIN LIMIT DYNAMIC QUERY OPTIMIZATION 【发布时间】:2019-11-16 04:05:44 【问题描述】:

这里是架构:-

Student_Details
ID | Student_id | Name          | Value
1  | 2          | City          | NewYork
2  | 2          | Height        | 5'11'
3  | 2          | Class         | B
4  | 2          | RollNo        | 265454
5  | 2          | Credit        | 800
6  | 3          | City          | Manila
7  | 3          | Height        | 5'10'
8  | 3          | Type          | International
9  | 3          | RollNo        | 2653
10 | 1          | Address       | Main Street
11 | 1          | Height        | 5'9'
12 | 1          | TST           | 21.54
13 | 1          | CCA           | 242
13 | 1          | SEX           | Male

Students 
ID | Name   | Age | AddedOn
1  | Alpha  | 27  | 20 Jan 2019
2  | Beta   | 15  | 19 Jan 2019
3  | Gamma  | 18  | 20 Jan 2019 

Student_Dep_Map
ID |DEP_ID | Student_id
 1 | 4 | 2
 2 | 2 | 1

Department 
ID| Name 
1 | Science
2 | Physics
3 | Chemistry 
4 | Psychology

所以搜索围绕着Students,我们需要用'City','Height' or 'TST',Student in which Departments,Added On,Age等各种搜索条件来搜索Students,基本上所有这些表. 动态搜索。此外,输出搜索还应包含每个表中的一些固定详细信息。所以我用 pivot 进行了这个查询。

select distinct s.*
FROM  students s
LEFT JOIN ( SELECT s.ID as s_id, group_concat(distinct d.name SEPARATOR ', ') as ASSIGNED_DEPT FROM students s   
          JOIN  Student_Dep_Map sdp JOIN  Department d on sdp.DEP_ID = d.ID ON sdp.Student_id=s.id  GROUP BY s.id  ) aa  ON aa.s_id = s.ID 
LEFT JOIN ( SELECT DISTINCT  student_id, 
            MAX(if(Name = 'TST', value, null)) as TST,   
                        MAX(if(Name = 'Height', value, null)) as HEIGHT, 
            MAX(if(Name = 'CITY', value, null)) as CITY, 
            MAX(if(Name = 'SEX',  value, null)) as SEX,
            MAX(if(Name = 'RollNo',  value, null)) as ROLLNO 
            FROM student_detials tip  GROUP BY  student_id ) sd ON sd.student_id = s.ID
WHERE 1=1 AND SD.SEX = 'Male' LIMIT 0,100 ;

AND 条件是通过查询生成器动态添加的。

挑战是!这个查询太慢了,随着表中记录的增加,查询变得非常慢。我确实使用了解释来理解查询计划。如果我离开索引&只谈论优化查询计划。 我注意到当我使用数据透视时,即使搜索条件来自主表学生,查询计划在透视学生详细信息时仍会遍历几乎所有记录。 有什么方法可以限制加入,直到找到 100 条学生记录。 一种方法是为每个表编写子查询以限制结果,但有没有更好的方法来处理这个? 我需要限制 joins 而不仅仅是 joins 之后的结果,因此 Query 会导致更快的获取。

注意:对不起,我不能发布真实的表格,它只是模拟数据。

【问题讨论】:

感谢您的回复。想加几分。正如我所说的搜索是动态的,搜索条件也可以属于学生表或部门,例如有多少学生属于物理系或有多少学生年龄小于 27 岁等。 我无法将这些“性别”、“身高”列添加到学生详细信息中,这是小样本数据,实际问题有更多 200 个唯一名称值。定义学生的对可能是学生完全独特的,也可能是每个学生都存在的领域。我们尝试在 student 表中添加任何属性。学生详细信息中有额外的属性。谢谢 另外,输出结果可以有固定的结果集,比如所有的s.*和一些student_Details的pivot属性,分配dept。 阅读 EAV 架构。您的变体非常笨拙且效率低下。 【参考方案1】:

我建议使用 exists 而不是聚合:

SELECT s.*,
       (SELECT GROUP_CONCAT(d.name SEPARATOR ', ')
        FROM Student_Dep_Map sdp JOIN
             Department d 
             ON sdp.DEP_ID = d.ID 
        WHERE sdp.Student_id = s.id
       ) as ASSIGNED_DEPT
FROM students s 
WHERE EXISTS (SELECT 1
              FROM student_details sd
              WHERE sd.student_id = s.id AND
                    sd.Name = 'SEX' AND
                    sd.Value = 'Male'
             )
LIMIT 0, 100 ;

为了性能,您需要以下索引:

Student_Dep_Map(student_id, dep_id) Departments(id, name)(可能存在,因为id应该是主键) student_details(student_id, name, value)

要添加更多条件,请添加更多EXISTS 子查询。

【讨论】:

是否应该将student_details 表转轴进行设计?似乎所有变量都描述了那个特定的学生。也就是说,设计应该是ID, Student_ID, City, Height, Class, RollNo, Credit, TST, CCA, SEX, ... @Cole 。 . . .在你的情况下听起来很贵。我认为您不想受到性能影响。为所有学生返回值(group by 有意义)与使用这些值进行过滤之间存在很大差异。 感谢您的回复。抱歉,我的意思是应该从根本上重新设计桌子吗?了解对每个查询进行透视是一项昂贵的操作。 @Cole 。 . .如果您知道表中有哪些列,您可能应该为每列使用一行。 EAV 方法不是最好的方法——类型可能不正确,您不能声明外键关系。 感谢它以不同的方式查看查询真的很有帮助。但是当搜索条件来自学生表时,我应该如何定义查询?我在原来的帖子中添加了几个 cmets。【参考方案2】:

如果您有兴趣从student_details 中选择某些字段,则另一种可能的途径。

SELECT s.*

FROM students s

LEFT OUTER JOIN
  (
    SELECT student_id 
    , MAX(if(Name = 'TST', value, null)) as TST   
    , MAX(if(Name = 'Height', value, null)) as HEIGHT 
    , MAX(if(Name = 'CITY', value, null)) as CITY 
    , MAX(if(Name = 'SEX',  value, null)) as SEX
    , MAX(if(Name = 'RollNo',  value, null)) as ROLLNO 

    FROM student_details tip  

    GROUP BY  student_id
  ) sd
  ON s.ID = sd.student_ID
   AND sd.SEX = 'Male'

LEFT OUTER JOIN 
  (
    SELECT sdp.ID as s_id
    , group_concat(d.name SEPARATOR ', ') as ASSIGNED_DEPT

    FROM Student_Dep_Map sdp
    INNER JOIN Department d ON sdp.DEP_ID = d.ID

    GROUP BY sdp.id
  ) aa
  ON aa.s_id = s.ID
;

您的原始查询还包含LEFT JOIN(...) WHERE sd.sex = 'Male',这将消除整个LEFT JOIN 部分。您可以通过以下方式简化它:

SELECT s.ID
, s.Name
, s.Age
, s.AddedOn
, group_concat(d.name SEPARATOR ', ') as ASSIGNED_DEPT
, sd.City

FROM students s
INNER JOIN student_dep_map sdp  ON s.ID = sdp.ID
INNER JOIN Department d     ON sdp.DEP_ID = d.ID
INNER JOIN 
  (
    SELECT student_id 
    , MAX(if(Name = 'TST', value, null)) as TST   
    , MAX(if(Name = 'Height', value, null)) as HEIGHT 
    , MAX(if(Name = 'CITY', value, null)) as CITY 
    , MAX(if(Name = 'SEX',  value, null)) as SEX
    , MAX(if(Name = 'RollNo',  value, null)) as ROLLNO 

    FROM student_details tip  

    GROUP BY  student_id
  ) sd
  ON s.ID = sd.student_ID

WHERE sd.SEX = 'Male'

GROUP BY s.ID
, s.Name
, s.Age
, s.AddedOn
, sd.City;

【讨论】:

以上是关于MySQL JOIN LIMIT 动态查询优化的主要内容,如果未能解决你的问题,请参考以下文章

mysql优化总结

MySQL 子查询与 LIMIT 与 JOIN

MySQL中limit使用动态参数的解决方法(拼接SQL字符串语句来执行SQL)

在python mysql查询中动态地传递列名和值。

MyBatis怎样实现MySQL动态分页

MYSQL优化 学习笔记