From 5f9a56011e1c190b26453b866f1df2dc81bb6223 Mon Sep 17 00:00:00 2001 From: Tim Hunt Date: Fri, 18 Jan 2013 16:43:48 +0000 Subject: [PATCH] MDL-37577 quiz overdue handling: optimise database query This is the standard trick of adding a redundant WHERE clause to a subquery to help the database realise that it does not have to inspect so many rows. --- mod/quiz/cronlib.php | 5 +++-- mod/quiz/locallib.php | 48 ++++++++++++++++++++++++++++--------------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/mod/quiz/cronlib.php b/mod/quiz/cronlib.php index c8776de38e7..175e5c6a1ac 100644 --- a/mod/quiz/cronlib.php +++ b/mod/quiz/cronlib.php @@ -100,7 +100,8 @@ class mod_quiz_overdue_attempt_updater { // SQL to compute timeclose and timelimit for each attempt: - $quizausersql = quiz_get_attempt_usertime_sql(); + $quizausersql = quiz_get_attempt_usertime_sql( + "iquiza.state IN ('inprogress', 'overdue') AND iquiza.timecheckstate <= :iprocessto"); // This query should have all the quiz_attempts columns. return $DB->get_recordset_sql(" @@ -116,6 +117,6 @@ class mod_quiz_overdue_attempt_updater { AND quiza.timecheckstate <= :processto ORDER BY quiz.course, quiza.quiz", - array('processto' => $processto)); + array('processto' => $processto, 'iprocessto' => $processto)); } } diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index ae23dd8e51b..ca78f679cd6 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -783,34 +783,48 @@ function quiz_update_open_attempts(array $conditions) { } $params = array(); - $coursecond = ''; - $usercond = ''; - $quizcond = ''; - $groupcond = ''; + $wheres = array("quiza.state IN ('inprogress', 'overdue')"); + $iwheres = array("iquiza.state IN ('inprogress', 'overdue')"); if (isset($conditions['courseid'])) { list ($incond, $inparams) = $DB->get_in_or_equal($conditions['courseid'], SQL_PARAMS_NAMED, 'cid'); $params = array_merge($params, $inparams); - $coursecond = "AND quiza.quiz IN (SELECT q.id FROM {quiz} q WHERE q.course $incond)"; + $wheres[] = "quiza.quiz IN (SELECT q.id FROM {quiz} q WHERE q.course $incond)"; + list ($incond, $inparams) = $DB->get_in_or_equal($conditions['courseid'], SQL_PARAMS_NAMED, 'icid'); + $params = array_merge($params, $inparams); + $iwheres[] = "iquiza.quiz IN (SELECT q.id FROM {quiz} q WHERE q.course $incond)"; } + if (isset($conditions['userid'])) { list ($incond, $inparams) = $DB->get_in_or_equal($conditions['userid'], SQL_PARAMS_NAMED, 'uid'); $params = array_merge($params, $inparams); - $usercond = "AND quiza.userid $incond"; + $wheres[] = "quiza.userid $incond"; + list ($incond, $inparams) = $DB->get_in_or_equal($conditions['userid'], SQL_PARAMS_NAMED, 'iuid'); + $params = array_merge($params, $inparams); + $iwheres[] = "iquiza.userid $incond"; } + if (isset($conditions['quizid'])) { list ($incond, $inparams) = $DB->get_in_or_equal($conditions['quizid'], SQL_PARAMS_NAMED, 'qid'); $params = array_merge($params, $inparams); - $quizcond = "AND quiza.quiz $incond"; + $wheres[] = "quiza.quiz $incond"; + list ($incond, $inparams) = $DB->get_in_or_equal($conditions['quizid'], SQL_PARAMS_NAMED, 'iqid'); + $params = array_merge($params, $inparams); + $iwheres[] = "iquiza.quiz $incond"; } + if (isset($conditions['groupid'])) { list ($incond, $inparams) = $DB->get_in_or_equal($conditions['groupid'], SQL_PARAMS_NAMED, 'gid'); $params = array_merge($params, $inparams); - $groupcond = "AND quiza.quiz IN (SELECT qo.quiz FROM {quiz_overrides} qo WHERE qo.groupid $incond)"; + $wheres[] = "quiza.quiz IN (SELECT qo.quiz FROM {quiz_overrides} qo WHERE qo.groupid $incond)"; + list ($incond, $inparams) = $DB->get_in_or_equal($conditions['groupid'], SQL_PARAMS_NAMED, 'igid'); + $params = array_merge($params, $inparams); + $iwheres[] = "iquiza.quiz IN (SELECT qo.quiz FROM {quiz_overrides} qo WHERE qo.groupid $incond)"; } // SQL to compute timeclose and timelimit for each attempt: - $quizausersql = quiz_get_attempt_usertime_sql(); + $quizausersql = quiz_get_attempt_usertime_sql( + implode("\n AND ", $iwheres)); // SQL to compute the new timecheckstate $timecheckstatesql = " @@ -822,11 +836,7 @@ function quiz_update_open_attempts(array $conditions) { CASE WHEN quiza.state = 'overdue' THEN quiz.graceperiod ELSE 0 END"; // SQL to select which attempts to process - $attemptselect = " quiza.state IN ('inprogress', 'overdue') - $coursecond - $usercond - $quizcond - $groupcond"; + $attemptselect = implode("\n AND ", $wheres); /* * Each database handles updates with inner joins differently: @@ -876,9 +886,14 @@ function quiz_update_open_attempts(array $conditions) { /** * Returns SQL to compute timeclose and timelimit for every attempt, taking into account user and group overrides. * - * @return string SQL select with columns attempt.id, usertimeclose, usertimelimit + * @param string $redundantwhereclauses extra where clauses to add to the subquery + * for performance. These can use the table alias iquiza for the quiz attempts table. + * @return string SQL select with columns attempt.id, usertimeclose, usertimelimit. */ -function quiz_get_attempt_usertime_sql() { +function quiz_get_attempt_usertime_sql($redundantwhereclauses = '') { + if ($redundantwhereclauses) { + $redundantwhereclauses = 'WHERE ' . $redundantwhereclauses; + } // The multiple qgo JOINS are necessary because we want timeclose/timelimit = 0 (unlimited) to supercede // any other group override $quizausersql = " @@ -894,6 +909,7 @@ function quiz_get_attempt_usertime_sql() { LEFT JOIN {quiz_overrides} qgo2 ON qgo2.quiz = iquiza.quiz AND qgo2.groupid = gm.groupid AND qgo2.timeclose > 0 LEFT JOIN {quiz_overrides} qgo3 ON qgo3.quiz = iquiza.quiz AND qgo3.groupid = gm.groupid AND qgo3.timelimit = 0 LEFT JOIN {quiz_overrides} qgo4 ON qgo4.quiz = iquiza.quiz AND qgo4.groupid = gm.groupid AND qgo4.timelimit > 0 + $redundantwhereclauses GROUP BY iquiza.id, iquiz.id, iquiz.timeclose, iquiz.timelimit"; return $quizausersql; }