Merge "rdbms: rename and clarify getTransactionLagStatus method regarding begin()"
[lhc/web/wiklou.git] / includes / libs / rdbms / database / DatabaseMysqlBase.php
index 8fb8db5..8fca440 100644 (file)
@@ -70,6 +70,12 @@ abstract class DatabaseMysqlBase extends Database {
        /** @var stdClass|null */
        private $replicationInfoRow = null;
 
+       // 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).
@@ -314,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
         */
@@ -558,17 +564,24 @@ abstract class DatabaseMysqlBase extends Database {
         * Takes same arguments as Database::select()
         *
         * @param string|array $table
-        * @param string|array $vars
+        * @param string|array $var
         * @param string|array $conds
         * @param string $fname
         * @param string|array $options
+        * @param array $join_conds
         * @return bool|int
         */
-       public function estimateRowCount( $table, $vars = '*', $conds = '',
-               $fname = __METHOD__, $options = []
+       public function estimateRowCount( $table, $var = '*', $conds = '',
+               $fname = __METHOD__, $options = [], $join_conds = []
        ) {
+               $conds = $this->normalizeConditions( $conds, $fname );
+               $column = $this->extractSingleFieldFromList( $var );
+               if ( is_string( $column ) && !in_array( $column, [ '*', '1' ] ) ) {
+                       $conds[] = "$column IS NOT NULL";
+               }
+
                $options['EXPLAIN'] = true;
-               $res = $this->select( $table, $vars, $conds, $fname, $options );
+               $res = $this->select( $table, $var, $conds, $fname, $options, $join_conds );
                if ( $res === false ) {
                        return false;
                }
@@ -739,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 );
                }
@@ -752,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'] );
@@ -846,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 {
@@ -891,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)
@@ -1281,10 +1319,6 @@ abstract class DatabaseMysqlBase extends Database {
                return $this->lastErrno() == 1205;
        }
 
-       public function wasErrorReissuable() {
-               return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
-       }
-
        /**
         * Determines if the last failure was due to the database being read-only.
         *
@@ -1418,40 +1452,6 @@ abstract class DatabaseMysqlBase extends Database {
                return in_array( $name, $this->listViews( $prefix ) );
        }
 
-       /**
-        * Allows for index remapping in queries where this is not consistent across DBMS
-        *
-        * @param string $index
-        * @return string
-        */
-       protected function indexName( $index ) {
-               /**
-                * When SQLite indexes were introduced in r45764, it was noted that
-                * SQLite requires index names to be unique within the whole database,
-                * not just within a schema. As discussed in CR r45819, to avoid the
-                * need for a schema change on existing installations, the indexes
-                * were implicitly mapped from the new names to the old names.
-                *
-                * This mapping can be removed if DB patches are introduced to alter
-                * the relevant tables in existing installations. Note that because
-                * this index mapping applies to table creation, even new installations
-                * of MySQL have the old names (except for installations created during
-                * a period where this mapping was inappropriately removed, see
-                * T154872).
-                */
-               $renamed = [
-                       'ar_usertext_timestamp' => 'usertext_timestamp',
-                       'un_user_id' => 'user_id',
-                       'un_user_ip' => 'user_ip',
-               ];
-
-               if ( isset( $renamed[$index] ) ) {
-                       return $renamed[$index];
-               } else {
-                       return $index;
-               }
-       }
-
        protected function isTransactableQuery( $sql ) {
                return parent::isTransactableQuery( $sql ) &&
                        !preg_match( '/^SELECT\s+(GET|RELEASE|IS_FREE)_LOCK\(/', $sql );