/** @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).
abstract protected function mysqlFetchObject( $res );
/**
- * @param ResultWrapper|resource $res
+ * @param IResultWrapper|resource $res
* @return array|bool
* @throws DBUnexpectedError
*/
* 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;
}
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 );
}
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'] );
// 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 {
$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)
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.
*
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 );