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 动态查询优化的主要内容,如果未能解决你的问题,请参考以下文章