From 7aadd1f2a155f584be3c8b3d95b081afbe74e4d3 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Tue, 23 Apr 2019 14:02:34 -0400 Subject: [PATCH] API: Add STRAIGHT_JOIN to ApiQueryUserContribs to avoid planner oddness For some unknown reason, when the `actor` table has few enough rows (or few enough compared to `revision_actor_temp`) MariaDB 10.1.37 decides it makes more sense to fetch everything from `actor` + `revision_actor_temp` and filesort rather than fetching the limited number of rows from `revision_actor_temp`. We can work around it by telling it to not reorder the query, but then (unlike in I9da981c0) we also have to reorder it ourselves to put `revision_actor_temp` first instead of `revision`. Bug: T221511 Change-Id: Ic63875b26a051a2da58374d5d76c95a6fa8ecc8e --- includes/api/ApiQueryUserContribs.php | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/includes/api/ApiQueryUserContribs.php b/includes/api/ApiQueryUserContribs.php index 5b178b7478..379f1afd39 100644 --- a/includes/api/ApiQueryUserContribs.php +++ b/includes/api/ApiQueryUserContribs.php @@ -331,9 +331,6 @@ class ApiQueryUserContribs extends ApiQueryBase { $db = $this->getDB(); $revQuery = MediaWikiServices::getInstance()->getRevisionStore()->getQueryInfo( [ 'page' ] ); - $this->addTables( $revQuery['tables'] ); - $this->addJoinConds( $revQuery['joins'] ); - $this->addFields( $revQuery['fields'] ); if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_READ_NEW ) { $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users ); @@ -341,6 +338,24 @@ class ApiQueryUserContribs extends ApiQueryBase { $userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name'; $tsField = 'revactor_timestamp'; $idField = 'revactor_rev'; + + // T221511: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor` + // before `revision_actor_temp` and filesorting is somehow better than querying $limit+1 rows + // from `revision_actor_temp`. Tell it not to reorder the query (and also reorder it ourselves + // because as generated by RevisionStore it'll have `revision` first rather than + // `revision_actor_temp`). But not when uctag is used, as it seems as likely to be harmed as + // helped in that case, and not when there's only one User because in that case it fetches + // the one `actor` row as a constant and doesn't filesort. + if ( count( $users ) > 1 && !isset( $this->params['tag'] ) ) { + $revQuery['joins']['revision'] = $revQuery['joins']['temp_rev_user']; + unset( $revQuery['joins']['temp_rev_user'] ); + $this->addOption( 'STRAIGHT_JOIN' ); + // It isn't actually necesssary to reorder $revQuery['tables'] as Database does the right thing + // when join conditions are given for all joins, but Gergő is wary of relying on that so pull + // `revision_actor_temp` to the start. + $revQuery['tables'] = + [ 'temp_rev_user' => $revQuery['tables']['temp_rev_user'] ] + $revQuery['tables']; + } } else { // If we're dealing with user names (rather than IDs) in read-old mode, // pass false for ActorMigration::getWhere()'s $useId parameter so @@ -353,6 +368,9 @@ class ApiQueryUserContribs extends ApiQueryBase { $idField = 'rev_id'; } + $this->addTables( $revQuery['tables'] ); + $this->addJoinConds( $revQuery['joins'] ); + $this->addFields( $revQuery['fields'] ); $this->addWhere( $revWhere['conds'] ); // Handle continue parameter -- 2.20.1