X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flibs%2Frdbms%2Fdatabase%2FDatabaseMysqlBase.php;h=bd2d274cc5e57ad34f16084c5b4a1531d9ccaa0a;hb=ee56f00ddf0609082f8ae9a4dc3e6e1b6f54ddfd;hp=305a056900abad5b9850e49c75b05058b5daef3d;hpb=394b04a0b208ee0f196ad73520c7cf6ed726b557;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/libs/rdbms/database/DatabaseMysqlBase.php b/includes/libs/rdbms/database/DatabaseMysqlBase.php index 305a056900..bd2d274cc5 100644 --- a/includes/libs/rdbms/database/DatabaseMysqlBase.php +++ b/includes/libs/rdbms/database/DatabaseMysqlBase.php @@ -24,7 +24,7 @@ namespace Wikimedia\Rdbms; use DateTime; use DateTimeZone; -use MediaWiki; +use Wikimedia; use InvalidArgumentException; use Exception; use stdClass; @@ -63,6 +63,8 @@ abstract class DatabaseMysqlBase extends Database { /** @var string|null */ private $serverVersion = null; + /** @var bool|null */ + private $insertSelectIsSafe = null; /** * Additional $params include: @@ -75,6 +77,7 @@ abstract class DatabaseMysqlBase extends Database { * ID of this server's master will be used. Set the "conds" field to * override the query conditions, e.g. ['shard' => 's1']. * - useGTIDs : use GTID methods like MASTER_GTID_WAIT() when possible. + * - insertSelectIsSafe : force that native INSERT SELECT is or is not safe [default: null] * - sslKeyPath : path to key file [default: null] * - sslCertPath : path to certificate file [default: null] * - sslCAFile: path to a single certificate authority PEM file [default: null] @@ -98,6 +101,8 @@ abstract class DatabaseMysqlBase extends Database { } $this->sqlMode = isset( $params['sqlMode'] ) ? $params['sqlMode'] : ''; $this->utf8Mode = !empty( $params['utf8Mode'] ); + $this->insertSelectIsSafe = isset( $params['insertSelectIsSafe'] ) + ? (bool)$params['insertSelectIsSafe'] : null; parent::__construct( $params ); } @@ -154,10 +159,10 @@ abstract class DatabaseMysqlBase extends Database { $this->reportConnectionError( $error ); } - if ( $dbName != '' ) { - MediaWiki\suppressWarnings(); + if ( strlen( $dbName ) ) { + Wikimedia\suppressWarnings(); $success = $this->selectDB( $dbName ); - MediaWiki\restoreWarnings(); + Wikimedia\restoreWarnings(); if ( !$success ) { $this->queryLogger->error( "Error selecting database {db_name} on server {db_server}", @@ -252,9 +257,9 @@ abstract class DatabaseMysqlBase extends Database { if ( $res instanceof ResultWrapper ) { $res = $res->result; } - MediaWiki\suppressWarnings(); + Wikimedia\suppressWarnings(); $ok = $this->mysqlFreeResult( $res ); - MediaWiki\restoreWarnings(); + Wikimedia\restoreWarnings(); if ( !$ok ) { throw new DBUnexpectedError( $this, "Unable to free MySQL result" ); } @@ -277,9 +282,9 @@ abstract class DatabaseMysqlBase extends Database { if ( $res instanceof ResultWrapper ) { $res = $res->result; } - MediaWiki\suppressWarnings(); + Wikimedia\suppressWarnings(); $row = $this->mysqlFetchObject( $res ); - MediaWiki\restoreWarnings(); + Wikimedia\restoreWarnings(); $errno = $this->lastErrno(); // Unfortunately, mysql_fetch_object does not reset the last errno. @@ -313,9 +318,9 @@ abstract class DatabaseMysqlBase extends Database { if ( $res instanceof ResultWrapper ) { $res = $res->result; } - MediaWiki\suppressWarnings(); + Wikimedia\suppressWarnings(); $row = $this->mysqlFetchArray( $res ); - MediaWiki\restoreWarnings(); + Wikimedia\restoreWarnings(); $errno = $this->lastErrno(); // Unfortunately, mysql_fetch_array does not reset the last errno. @@ -349,9 +354,9 @@ abstract class DatabaseMysqlBase extends Database { if ( $res instanceof ResultWrapper ) { $res = $res->result; } - MediaWiki\suppressWarnings(); + Wikimedia\suppressWarnings(); $n = $this->mysqlNumRows( $res ); - MediaWiki\restoreWarnings(); + Wikimedia\restoreWarnings(); // Unfortunately, mysql_num_rows does not reset the last errno. // We are not checking for any errors here, since @@ -462,12 +467,12 @@ abstract class DatabaseMysqlBase extends Database { public function lastError() { if ( $this->mConn ) { # Even if it's non-zero, it can still be invalid - MediaWiki\suppressWarnings(); + Wikimedia\suppressWarnings(); $error = $this->mysqlError( $this->mConn ); if ( !$error ) { $error = $this->mysqlError(); } - MediaWiki\restoreWarnings(); + Wikimedia\restoreWarnings(); } else { $error = $this->mysqlError(); } @@ -501,6 +506,56 @@ abstract class DatabaseMysqlBase extends Database { return $this->nativeReplace( $table, $rows, $fname ); } + protected function nativeInsertSelect( + $destTable, $srcTable, $varMap, $conds, + $fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = [] + ) { + if ( $this->insertSelectIsSafe === null ) { + // In MySQL, an INSERT SELECT is only replication safe with row-based + // replication or if innodb_autoinc_lock_mode is 0. When those + // conditions aren't met, use non-native mode. + // While we could try to determine if the insert is safe anyway by + // checking if the target table has an auto-increment column that + // isn't set in $varMap, that seems unlikely to be worth the extra + // complexity. + $row = $this->selectRow( + false, + [ + 'innodb_autoinc_lock_mode' => '@@innodb_autoinc_lock_mode', + 'binlog_format' => '@@binlog_format', + ], + [], + __METHOD__ + ); + $this->insertSelectIsSafe = $row->binlog_format === 'ROW' || + (int)$row->innodb_autoinc_lock_mode === 0; + } + + if ( !$this->insertSelectIsSafe ) { + return $this->nonNativeInsertSelect( + $destTable, + $srcTable, + $varMap, + $conds, + $fname, + $insertOptions, + $selectOptions, + $selectJoinConds + ); + } else { + return parent::nativeInsertSelect( + $destTable, + $srcTable, + $varMap, + $conds, + $fname, + $insertOptions, + $selectOptions, + $selectJoinConds + ); + } + } + /** * Estimate rows in dataset * Returns estimated count, based on EXPLAIN output @@ -834,8 +889,10 @@ abstract class DatabaseMysqlBase extends Database { return 0; // already reached this point for sure } + $useGTID = ( $this->useGTIDs && $pos->gtids ); + // Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set - if ( $this->useGTIDs && $pos->gtids ) { + if ( $useGTID ) { // Wait on the GTID set (MariaDB only) $gtidArg = $this->addQuotes( implode( ',', $pos->gtids ) ); $res = $this->doQuery( "SELECT MASTER_GTID_WAIT($gtidArg, $timeout)" ); @@ -855,14 +912,17 @@ abstract class DatabaseMysqlBase extends Database { // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual $status = ( $row[0] !== null ) ? intval( $row[0] ) : null; if ( $status === null ) { - // T126436: jobs programmed to wait on master positions might be referencing binlogs - // with an old master hostname. Such calls make MASTER_POS_WAIT() return null. Try - // to detect this and treat the replica DB as having reached the position; a proper master - // switchover already requires that the new master be caught up before the switch. - $replicationPos = $this->getReplicaPos(); - if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) { - $this->lastKnownReplicaPos = $replicationPos; - $status = 0; + if ( !$useGTID ) { + // T126436: jobs programmed to wait on master positions might be referencing + // binlogs with an old master hostname; this makes MASTER_POS_WAIT() return null. + // Try to detect this case and treat the replica DB as having reached the given + // position (any master switchover already requires that the new master be caught + // up before the switch). + $replicationPos = $this->getReplicaPos(); + if ( $replicationPos && !$replicationPos->channelsMatch( $pos ) ) { + $this->lastKnownReplicaPos = $replicationPos; + $status = 0; + } } } elseif ( $status >= 0 ) { // Remember that this position was reached to save queries next time @@ -882,9 +942,7 @@ abstract class DatabaseMysqlBase extends Database { $row = $this->fetchObject( $res ); if ( $row ) { - $pos = isset( $row->Exec_master_log_pos ) - ? $row->Exec_master_log_pos - : $row->Exec_Master_Log_Pos; + $pos = $row->Exec_Master_Log_Pos; // Also fetch the last-applied GTID set (MariaDB) if ( $this->useGTIDs ) { $res = $this->query( "SHOW GLOBAL VARIABLES LIKE 'gtid_slave_pos'", __METHOD__ );