Avoid TransactionProfiler notices for TEMPORARY table writes
authorAaron Schulz <aschulz@wikimedia.org>
Mon, 19 Sep 2016 21:15:05 +0000 (14:15 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Mon, 19 Sep 2016 23:49:56 +0000 (16:49 -0700)
* Make Database detect and track temporary tables for the session.
  Creating, dropping, and updating them do not count as "write" queries.
  Even a read-only mysql slave can have these operations performed on it
  and it can be useful for complex read queries that need temporary results.
* Rename handleTransactionLoss() to handleSessionLoss() and cover named locks too.

Bug: T145947
Change-Id: I826439e9e9f550f32a9c46b3dd60e8e8015aa274

includes/libs/rdbms/database/Database.php

index f9e9296..538d0b8 100644 (file)
@@ -203,6 +203,8 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
 
        /** @var array Map of (name => 1) for locks obtained via lock() */
        private $mNamedLocksHeld = [];
+       /** @var array Map of (table name => 1) for TEMPORARY tables */
+       private $mSessionTempTables = [];
 
        /** @var IDatabase|null Lazy handle to the master DB this server replicates from */
        private $lazyMasterHandle;
@@ -772,11 +774,40 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return !in_array( $verb, [ 'BEGIN', 'COMMIT', 'ROLLBACK', 'SHOW', 'SET' ], true );
        }
 
+       /**
+        * @param string $sql A SQL query
+        * @return bool Whether $sql is SQL for creating/dropping a new TEMPORARY table
+        */
+       protected function registerTempTableOperation( $sql ) {
+               if ( preg_match(
+                       '/^(CREATE|DROP)\s+TEMPORARY\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"\']?(\w+)[`"\']?/i',
+                       $sql,
+                       $matches
+               ) ) {
+                       list( , $verb, $table ) = $matches;
+                       if ( $verb === 'CREATE' ) {
+                               $this->mSessionTempTables[$table] = 1;
+                       } else {
+                               unset( $this->mSessionTempTables[$table] );
+                       }
+
+                       return true;
+               } elseif ( preg_match(
+                       '/^(?:INSERT\s+(?:\w+\s+)?INTO|UPDATE|DELETE\s+FROM)\s+[`"\']?(\w+)[`"\']?/i',
+                       $sql,
+                       $matches
+               ) ) {
+                       return isset( $this->mSessionTempTables[$matches[1]] );
+               }
+
+               return false;
+       }
+
        public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
                $priorWritesPending = $this->writesOrCallbacksPending();
                $this->mLastQuery = $sql;
 
-               $isWrite = $this->isWriteQuery( $sql );
+               $isWrite = $this->isWriteQuery( $sql ) && !$this->registerTempTableOperation( $sql );
                if ( $isWrite ) {
                        $reason = $this->getReadOnlyReason();
                        if ( $reason !== false ) {
@@ -822,7 +853,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                        $lastError = $this->lastError();
                        $lastErrno = $this->lastErrno();
                        # Update state tracking to reflect transaction loss due to disconnection
-                       $this->handleTransactionLoss();
+                       $this->handleSessionLoss();
                        if ( $this->reconnect() ) {
                                $msg = __METHOD__ . ": lost connection to {$this->getServer()}; reconnected";
                                $this->connLogger->warning( $msg );
@@ -851,7 +882,7 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                                        $tempIgnore = false; // not recoverable
                                }
                                # Update state tracking to reflect transaction loss
-                               $this->handleTransactionLoss();
+                               $this->handleSessionLoss();
                        }
 
                        $this->reportQueryError(
@@ -964,10 +995,12 @@ abstract class Database implements IDatabase, LoggerAwareInterface {
                return true;
        }
 
-       private function handleTransactionLoss() {
+       private function handleSessionLoss() {
                $this->mTrxLevel = 0;
                $this->mTrxIdleCallbacks = []; // bug 65263
                $this->mTrxPreCommitCallbacks = []; // bug 65263
+               $this->mSessionTempTables = [];
+               $this->mNamedLocksHeld = [];
                try {
                        // Handle callbacks in mTrxEndCallbacks
                        $this->runOnTransactionIdleCallbacks( self::TRIGGER_ROLLBACK );