MDL-68183 auth: Fix the performance of get_complete_user_data search
authorDavid Mudrák <david@moodle.com>
Tue, 17 Mar 2020 13:44:50 +0000 (14:44 +0100)
committerDavid Mudrák <david@moodle.com>
Tue, 17 Mar 2020 13:52:32 +0000 (14:52 +0100)
When searching for the user by a case-insensitive field (email address
is the only one supported now), the performance may be very poor as the
DB cannot use the index due to the LOWER() operation and the full
sequential scan of all the user records is performed. On some DBs such
as MySQL, this can be significantly improved by pre-filtering the users
with accent-insensitive search.

So we first perform accent-insensitive search for potential candidates
in a subselect, which can use the index. Only then we perform the
additional accent-sensitive search on this limited set or records.

lib/moodlelib.php

index e879546..5441dbd 100644 (file)
@@ -4913,11 +4913,15 @@ function get_complete_user_data($field, $value, $mnethostid = null, $throwexcept
     // Build the WHERE clause for an SQL query.
     $params = array('fieldval' => $value);
 
-    // Do a case-insensitive query, if necessary.
+    // Do a case-insensitive query, if necessary. These are generally very expensive. The performance can be improved on some DBs
+    // such as MySQL by pre-filtering users with accent-insensitive subselect.
     if (in_array($field, $caseinsensitivefields)) {
         $fieldselect = $DB->sql_equal($field, ':fieldval', false);
+        $idsubselect = $DB->sql_equal($field, ':fieldval2', false, false);
+        $params['fieldval2'] = $value;
     } else {
         $fieldselect = "$field = :fieldval";
+        $idsubselect = '';
     }
     $constraints = "$fieldselect AND deleted <> 1";
 
@@ -4934,6 +4938,10 @@ function get_complete_user_data($field, $value, $mnethostid = null, $throwexcept
         $constraints .= " AND mnethostid = :mnethostid";
     }
 
+    if ($idsubselect) {
+        $constraints .= " AND id IN (SELECT id FROM {user} WHERE {$idsubselect})";
+    }
+
     // Get all the basic user data.
     try {
         // Make sure that there's only a single record that matches our query.