// 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
*/
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)
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 );
}
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;
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