Merge "Test ApiUserrights"
[lhc/web/wiklou.git] / includes / libs / rdbms / database / DatabaseMysqlBase.php
index 286d658..3e6190c 100644 (file)
@@ -73,6 +73,9 @@ abstract class DatabaseMysqlBase extends Database {
        // Cache getServerId() for 24 hours
        const SERVER_ID_CACHE_TTL = 86400;
 
+       /** @var float Warn if lag estimates are made for transactions older than this many seconds */
+       const LAG_STALE_WARN_THRESHOLD = 0.100;
+
        /**
         * Additional $params include:
         *   - lagDetectionMethod : set to one of (Seconds_Behind_Master,pt-heartbeat).
@@ -317,7 +320,7 @@ abstract class DatabaseMysqlBase extends Database {
        abstract protected function mysqlFetchObject( $res );
 
        /**
-        * @param ResultWrapper|resource $res
+        * @param IResultWrapper|resource $res
         * @return array|bool
         * @throws DBUnexpectedError
         */
@@ -749,6 +752,7 @@ abstract class DatabaseMysqlBase extends Database {
        protected function getLagFromSlaveStatus() {
                $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
                $row = $res ? $res->fetchObject() : false;
+               // If the server is not replicating, there will be no row
                if ( $row && strval( $row->Seconds_Behind_Master ) !== '' ) {
                        return intval( $row->Seconds_Behind_Master );
                }
@@ -762,6 +766,22 @@ abstract class DatabaseMysqlBase extends Database {
        protected function getLagFromPtHeartbeat() {
                $options = $this->lagDetectionOptions;
 
+               $currentTrxInfo = $this->getRecordedTransactionLagStatus();
+               if ( $currentTrxInfo ) {
+                       // There is an active transaction and the initial lag was already queried
+                       $staleness = microtime( true ) - $currentTrxInfo['since'];
+                       if ( $staleness > self::LAG_STALE_WARN_THRESHOLD ) {
+                               // Avoid returning higher and higher lag value due to snapshot age
+                               // given that the isolation level will typically be REPEATABLE-READ
+                               $this->queryLogger->warning(
+                                       "Using cached lag value for {db_server} due to active transaction",
+                                       $this->getLogContext( [ 'method' => __METHOD__, 'age' => $staleness ] )
+                               );
+                       }
+
+                       return $currentTrxInfo['lag'];
+               }
+
                if ( isset( $options['conds'] ) ) {
                        // Best method for multi-DC setups: use logical channel names
                        $data = $this->getHeartbeatData( $options['conds'] );
@@ -856,7 +876,8 @@ abstract class DatabaseMysqlBase extends Database {
                        // Note: this would use "TIMESTAMPDIFF(MICROSECOND,ts,UTC_TIMESTAMP(6))" but the
                        // percision field is not supported in MySQL <= 5.5.
                        $res = $this->query(
-                               "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1"
+                               "SELECT ts FROM heartbeat.heartbeat WHERE $whereSQL ORDER BY ts DESC LIMIT 1",
+                               __METHOD__
                        );
                        $row = $res ? $res->fetchObject() : false;
                } finally {
@@ -901,6 +922,13 @@ abstract class DatabaseMysqlBase extends Database {
                        $rpos = $this->getReplicaPos();
                        $gtidsWait = $rpos ? MySQLMasterPos::getCommonDomainGTIDs( $pos, $rpos ) : [];
                        if ( !$gtidsWait ) {
+                               $this->queryLogger->error(
+                                       "No GTIDs with the same domain between master ($pos) and replica ($rpos)",
+                                       $this->getLogContext( [
+                                               'method' => __METHOD__,
+                                       ] )
+                               );
+
                                return -1; // $pos is from the wrong cluster?
                        }
                        // Wait on the GTID set (MariaDB only)
@@ -958,8 +986,8 @@ abstract class DatabaseMysqlBase extends Database {
                if ( $this->useGTIDs() ) {
                        // Try to use GTIDs, fallbacking to binlog positions if not possible
                        $data = $this->getServerGTIDs( __METHOD__ );
-                       // Use gtid_current_pos for MariaDB and gtid_executed for MySQL
-                       foreach ( [ 'gtid_current_pos', 'gtid_executed' ] as $name ) {
+                       // Use gtid_slave_pos for MariaDB and gtid_executed for MySQL
+                       foreach ( [ 'gtid_slave_pos', 'gtid_executed' ] as $name ) {
                                if ( isset( $data[$name] ) && strlen( $data[$name] ) ) {
                                        return new MySQLMasterPos( $data[$name], $now );
                                }
@@ -989,8 +1017,8 @@ abstract class DatabaseMysqlBase extends Database {
                if ( $this->useGTIDs() ) {
                        // Try to use GTIDs, fallbacking to binlog positions if not possible
                        $data = $this->getServerGTIDs( __METHOD__ );
-                       // Use gtid_current_pos for MariaDB and gtid_executed for MySQL
-                       foreach ( [ 'gtid_current_pos', 'gtid_executed' ] as $name ) {
+                       // Use gtid_binlog_pos for MariaDB and gtid_executed for MySQL
+                       foreach ( [ 'gtid_binlog_pos', 'gtid_executed' ] as $name ) {
                                if ( isset( $data[$name] ) && strlen( $data[$name] ) ) {
                                        $pos = new MySQLMasterPos( $data[$name], $now );
                                        break;
@@ -1386,6 +1414,26 @@ abstract class DatabaseMysqlBase extends Database {
                return $errno == 2013 || $errno == 2006;
        }
 
+       protected function wasKnownStatementRollbackError() {
+               $errno = $this->lastErrno();
+
+               if ( $errno === 1205 ) { // lock wait timeout
+                       // Note that this is uncached to avoid stale values of SET is used
+                       $row = $this->selectRow(
+                               false,
+                               [ 'innodb_rollback_on_timeout' => '@@innodb_rollback_on_timeout' ],
+                               [],
+                               __METHOD__
+                       );
+                       // https://dev.mysql.com/doc/refman/5.7/en/innodb-error-handling.html
+                       // https://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html
+                       return $row->innodb_rollback_on_timeout ? false : true;
+               }
+
+               // See https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
+               return in_array( $errno, [ 1022, 1216, 1217, 1137 ], true );
+       }
+
        /**
         * @param string $oldName
         * @param string $newName