MySQL - 简单的订单表现不佳

时间:2016-02-11 21:37:24

标签: mysql sql sql-order-by

表格(InnoDB):

  • 学生 - 400.000行(35.000无效)
  • CLASS - 40.000行
  • CLASS_STUDENT - 460.000行

查询:

SELECT S.* 
FROM STUDENT S
JOIN CLASS_STUDENT CS ON CS.STUDENT_ID = S.STUDENT_ID 
JOIN CLASS C ON C.CLASS_ID = CS.CLASS_ID
WHERE S.ACTIVE = TRUE
GROUP BY S.STUDENT_ID --this suffices to fetch students only once
ORDER BY C.CLASS_DATE DESC --datetime field
LIMIT 0,5

ORDER BY的执行时间:3.2297秒

没有ORDER BY的执行时间:0.0015秒

我的系统中有3秒钟会导致用户体验不佳。有没有办法用ORDER BY加速此查询? LIMIT用于分页。我是按CLASS_DATE desc订购的,因为我希望在我的分页结果中首先看到参加最近课程的学生。

我无法删除我正在使用的联接

谢谢!

编辑:EXPLAIN两个查询:

EXPLAIN for both queries

EDIT2:innodb_buffer_pool_size = 4GB,我的服务器中有16 GB

2 个答案:

答案 0 :(得分:1)

如果这是我的项目,我只会列出每个学生一次。不是多次,在注册(CLASS_STUDENT)表格中为每一行重复学生。

我会认真考虑通过向STUDENT表添加派生列来对数据库实现进行非规范化,例如

 ALTER TABLE STUDENT ADD latest_class_date DATE DEFAULT NULL;

填充该栏目:

 UPDATE STUDENT t
   LEFT
   JOIN ( SELECT cs.student_id
               , MAX(c.class_date) AS latest_class_date
            FROM CLASS_STUDENT cs
            JOIN CLASS c
              ON c.class_id = cs.class_id
           GROUP BY cs.student_id
        ) s
     ON t.student_id = s.student_id   
    SET t.latest_class_date = s.latest_class_date

添加新列后,我可以创建一个合适的索引,例如

... ON STUDENT (active, latest_class_date, student_id)

然后我的查询更简单:

SELECT s.*
  FROM student s
 WHERE s.active = 1
 ORDER BY s.active DESC, s.latest_class_date DESC, student_id DESC
 LIMIT 5

我在student_id中加入了ORDER BY,以使结果具有确定性。 (没有它,MySQL可以按任何顺序自由返回任何具有相同latest_class_date的行。)

我还可以更有效地实现分页,保留上一个先前检索的行中的值,并在查询中提供这些值。获得"接下来的5行":

编辑:原版中的分页查询模式显然是错误的。这已在此处和后续查询中进行了更正。)

SELECT s.*
  FROM student s
 WHERE s.active = 1
   AND s.latest_class_date <= ?
   AND (s.latest_class_date < ? OR s.student_id < ? )
 ORDER BY s.active DESC, s.latest_class_date DESC, student_id DESC
 LIMIT 5

需要维护新latest_class_date列的内容。有几个选择。

如果我可以忍受该列内容可能不同步的一段时间,那么

  • 安排执行update语句以定期刷新该列的内容。

如果我要求保持该列内容的同步,则可以:

  • 修改管理student,class和student_class表的应用程序,以确保在latest_class_dateCLASS表中添加/更改/删除行时填充STUDENT_CLASS列,或

  • 在表格上添加触发器以保持该列的填充

    • BEFORE INSERT/UPDATE触发STUDENT
    • AFTER INSERT/UPDATE/DELETE触发CLASS_STUDENT
    • AFTER INSERT/UPDATE/AFTER DELETE触发CLASS

(我需要记住,外键操​​作不会触发触发器。例如,如果从{{{} {{}}}中删除行,则从CLASS_STUDENT删除行作为FOREIGN KEY的CASCADE操作的结果1}},那么只会触发CLASS表的触发器。这意味着我必须在CLASS表的删除触发器中处理所需的操作。)

<强>后续

如果您需要所有联接&#34;由于这些表格中的其他信息你可能需要一天的时间,我上面提出的建议并没有多大帮助。在性能,甜甜圈的美元方面,&#34;使用filesort&#34;在一个吃午饭的大集合上操作。在我建议的查询中添加联接并不能避免这种情况&#34;使用filesort&#34;操作

如果我建议的查询有合理的性能,那么我们可以将这个查询用作内联视图,以限制返回的行数,然后再进行连接。

但在我们这样做之前,我们必须首先解决当注册(CLASS)表中有多行时返回的重复学生行。我们希望同一个学生多次返回吗?或者我们想要多次返回学生行,对于CLASS_STUDENT的每一行,对于具有相同CLASS_STUDENT的班级,一次。或者,我们是否只想返回学生行一次,只有一个 class_date的信息?如果我们为学生返回多行,是打算每页列出五个学生,还是每页列出五个student_class?

假设&#34;分页&#34;每页五行,我们期待这样的结果集吗?

第1行至第5行

CLASS

第6行至第10行

student    class   class_date
-------    -----   ---------- 
Sam        phys    2016-02-12
Sam        calc    2016-02-12
Mary       lit     2016-02-12
Mahatma    art     2016-02-12
Paul       music   2016-02-11

我们编写的查询将由规范通知。

我们可以写出无数可能的查询。但是如果没有规范,那些查询中的每一个都只是猜测。在不知道返回的结果集的情况下(在各种可能的条件下),我们无法验证我们编写的查询是否正确。

我再次查看了您的查询,并注意到您确实有Paul engl 2016-02-11 Sam art 2016-02-10 ... 。 (我们将假设GROUP BY student_id表中student_id是唯一的。)

如果学生的students表格中有多行,并且与CLASS_STUDENT相关的CLASS行的class_date值不同,则class_date的值在原始查询中返回的是 indeterminate 。 MySQL可以自由选择任何可能的class_date值。 (它不只是class_date列......来自CLASSCLASS_STUDENT的行中的值是不确定的。)

使用原始查询,无法保证学生使用&#34;最新的&#34; class_date将在其他学生之前列出。例如,使用此集:

student   class_date
-------   ----------
Sam       2016-02-22 
Sam       2015-07-17
Paul      2016-01-11

上面的查询可以在Sam之前订Paul行,在Sam之后订Paul行。结果对原始查询有效。并且您无法保证每次运行查询时结果都是相同的。结果是不确定

其他数据库会在原始查询中引发错误,而SELECT行列表中的非聚合不出现在GROUP BY子句&#34;中。 MySQL特定的扩展允许查询执行。通过在ONLY_FULL_GROUP_BY中加入sql_mode,可以让MySQL的行为与其他数据库相同,并抛出错误。

SELECT d.*
  FROM ( SELECT s.student_id
              , s.latest_class_date
           FROM student s
          WHERE s.active = 1
            AND s.latest_class_date <= ? 
            AND ( s.latest_class_date < ? OR s.student_id < ? )
          ORDER BY s.active DESC, s.latest_class_date DESC, student_id DESC
          LIMIT 5
       ) r
  JOIN student d
    ON d.student_id = r.student_id
  JOIN class_student e
    ON e.student_id = d.student_id
  JOIN class c
    ON c.class_id = e.class_id
   AND c.class_date = r.latest_class_date
 GROUP BY d.student_id

答案 1 :(得分:0)

尝试在加入学生之前过滤:

SELECT S.* 
FROM STUDENT S
JOIN 
 ( select CS.STUDENT_ID, MAX(C.CLASS_DATE) AS maxDate
   from CLASS_STUDENT CS 
   JOIN CLASS C ON C.CLASS_ID = CS.CLASS_ID
   GROUP BY CS.STUDENT_ID
   ORDER BY maxDate DESC
    -- this might include non-active students
    -- but hopefully returns at least 5 students with S.ACTIVE = TRUE
   LIMIT 0,10
 ) dt
ON dt.STUDENT_ID = S.STUDENT_ID 
WHERE S.ACTIVE = TRUE
ORDER BY dt.maxDate DESC
LIMIT 0,5

如果有很多不活跃的学生(400.000中有35.000)你可能需要增加内部限制,但另一方面,不活跃的学生可能不会参加最近的课程: - )