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.
This commit is contained in:
Tim Hunt 2013-01-18 16:43:48 +00:00
parent e61f96c55b
commit 5f9a56011e
2 changed files with 35 additions and 18 deletions

View file

@ -100,7 +100,8 @@ class mod_quiz_overdue_attempt_updater {
// SQL to compute timeclose and timelimit for each attempt: // 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. // This query should have all the quiz_attempts columns.
return $DB->get_recordset_sql(" return $DB->get_recordset_sql("
@ -116,6 +117,6 @@ class mod_quiz_overdue_attempt_updater {
AND quiza.timecheckstate <= :processto AND quiza.timecheckstate <= :processto
ORDER BY quiz.course, quiza.quiz", ORDER BY quiz.course, quiza.quiz",
array('processto' => $processto)); array('processto' => $processto, 'iprocessto' => $processto));
} }
} }

View file

@ -783,34 +783,48 @@ function quiz_update_open_attempts(array $conditions) {
} }
$params = array(); $params = array();
$coursecond = ''; $wheres = array("quiza.state IN ('inprogress', 'overdue')");
$usercond = ''; $iwheres = array("iquiza.state IN ('inprogress', 'overdue')");
$quizcond = '';
$groupcond = '';
if (isset($conditions['courseid'])) { if (isset($conditions['courseid'])) {
list ($incond, $inparams) = $DB->get_in_or_equal($conditions['courseid'], SQL_PARAMS_NAMED, 'cid'); list ($incond, $inparams) = $DB->get_in_or_equal($conditions['courseid'], SQL_PARAMS_NAMED, 'cid');
$params = array_merge($params, $inparams); $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'])) { if (isset($conditions['userid'])) {
list ($incond, $inparams) = $DB->get_in_or_equal($conditions['userid'], SQL_PARAMS_NAMED, 'uid'); list ($incond, $inparams) = $DB->get_in_or_equal($conditions['userid'], SQL_PARAMS_NAMED, 'uid');
$params = array_merge($params, $inparams); $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'])) { if (isset($conditions['quizid'])) {
list ($incond, $inparams) = $DB->get_in_or_equal($conditions['quizid'], SQL_PARAMS_NAMED, 'qid'); list ($incond, $inparams) = $DB->get_in_or_equal($conditions['quizid'], SQL_PARAMS_NAMED, 'qid');
$params = array_merge($params, $inparams); $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'])) { if (isset($conditions['groupid'])) {
list ($incond, $inparams) = $DB->get_in_or_equal($conditions['groupid'], SQL_PARAMS_NAMED, 'gid'); list ($incond, $inparams) = $DB->get_in_or_equal($conditions['groupid'], SQL_PARAMS_NAMED, 'gid');
$params = array_merge($params, $inparams); $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: // 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 // SQL to compute the new timecheckstate
$timecheckstatesql = " $timecheckstatesql = "
@ -822,11 +836,7 @@ function quiz_update_open_attempts(array $conditions) {
CASE WHEN quiza.state = 'overdue' THEN quiz.graceperiod ELSE 0 END"; CASE WHEN quiza.state = 'overdue' THEN quiz.graceperiod ELSE 0 END";
// SQL to select which attempts to process // SQL to select which attempts to process
$attemptselect = " quiza.state IN ('inprogress', 'overdue') $attemptselect = implode("\n AND ", $wheres);
$coursecond
$usercond
$quizcond
$groupcond";
/* /*
* Each database handles updates with inner joins differently: * 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. * 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 // The multiple qgo JOINS are necessary because we want timeclose/timelimit = 0 (unlimited) to supercede
// any other group override // any other group override
$quizausersql = " $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} 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} 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 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"; GROUP BY iquiza.id, iquiz.id, iquiz.timeclose, iquiz.timelimit";
return $quizausersql; return $quizausersql;
} }