2

I'm not sure if this is a bug in PHP, MySQL or me being stupid but very occasionally (and it is very rare) we call mysqli::query and don't get FALSE or a mysqli_result object back.

We are running:

  • PHP 5.5.9-1ubuntu4.3
  • MySQL Ver 15.1 Distrib 5.5.38-MariaDB, for debian-linux-gnu (x86_64) using readline 5.1 (using Galera)

The query run was SHOW STATUS LIKE "Questions".

According to the manual for a SHOW query we should ALWAYS get back either FALSE or a mysqli_result object, but we don't get either. This line of code runs over 100,000 times each day and fails about once a week.

Here is a snippet of code from our database wrapper (the function is only ever used for read queries):

public function read($query, $cache = 0) {
    $total_time_start = microtime(1);

    $deadlock_retries_done = 0;
    $deadlock_retries_max = 10;
    $other_error_retries_done = 0;
    $other_error_retries_max = 10;

    $return = array(); // Keeps IDEs happy!

    while (true) {
        $start = microtime(1);
        $return = array();
        $q = $this->connection->query($query, MYSQLI_STORE_RESULT);
        $this->debug_last_query = $query;

        // Was there an error?
        if ($q === false) {
            $error = $this->connection->error;
            switch ($error) {
                case 'Deadlock found when trying to get lock; try restarting transaction':
                case 'Lock wait timeout exceeded; try restarting transaction':
                    $deadlock_retries_done++;
                    if ($deadlock_retries_done == $deadlock_retries_max) {
                        throw new SQLException($error . '. Re-tried with deadlock ' . $deadlock_retries_done . ' times. Query: ' . $query);
                    } else {
                        continue; // Try again
                    }
                    break;

                case 'WSREP has not yet prepared node for application use':
                    $other_error_retries_done++;
                    if ($other_error_retries_done == $other_error_retries_max) {
                        throw new SQLException($error . '. Re-tried with error ' . $other_error_retries_done . ' times. Query: ' . $query);
                    } else {
                        if ($this->in_transaction) {
                            throw new SQLException($error . '. Re-tried with error ' . $other_error_retries_done . ' times. Cannot reconnect as in transaction. Query: ' . $query);
                        } else {
                            $this->close_and_establish_new_database_connection();
                            continue; // Try again
                        }
                    }
                    break;

                default:
                    throw new SQLException($error . '. Query: ' . $query);
                    break;
            }
        }

        // Check we have got a result
        if (!$q instanceof mysqli_result) {
            throw new SQLException('Seemed to have a result but it is not a mysqli_result object. Query: ' . $query);
        }

        // All worked ok, deal with the result
        while (($row = $q->fetch_assoc())) {
            $return[] = $row;
        }
        $end = microtime(1);

        $this->debugData($start, $end, $query, 'DB', '', $total_time_start, $cache);

        $this->last_affected_rows = $q->num_rows;
        $q->free_result();
        break;
    }

    return $return;
}

Obviously it calls some functions that are not included in the snippet, but you should get the idea.

The exception thrown is:

SQLException: Seemed to have a result but it is not a mysqli_result object. Query: SHOW STATUS LIKE "Questions"

I will add to our exception message something to output what $q actually is and wait for it to happen again.

Has anyone else every experienced something like this and do you have anything to suggest? I really appreciate your help. Thanks


EDIT:

We have just had it happen on a really simple SELECT cols FROM table WHERE key = x LIMIT 1; type query (real query not shown for security reasons, but it was about the most simple query you could have). This happened before my extra logging had gone live. I'll update here again when I get another with hopefully more details.

Luke Cousins
  • 2,068
  • 1
  • 20
  • 38

1 Answers1

1

In your code, if $q is false, it will still comes into the if condition of:

    if (!$q instanceof mysqli_result) {

Maybe you need else if instead.

xdazz
  • 158,678
  • 38
  • 247
  • 274
  • No it wont. If `$q === false` then it will hit the section above and either throw an earlier exception or `continue;` into another iteration of the loop. – Luke Cousins Sep 05 '14 at 10:02
  • My apologies, you were right that `$q` could be false. I was expecting the `continue;` in the switch to be ignored by the `switch` and continue the `while` loop. Actually `continue;` in a switch is the same as a `break;` http://stackoverflow.com/a/12349889/1178671 so that explains it. The `continue;` should be `continue 2;`. Again sorry for my incorrect comment above. – Luke Cousins Sep 05 '14 at 14:49