0

I can not figure out what is happening with my Laravel script. I'm simply selecting an object, and calling a method on it that runs additional queries, but the behavior I'm seeing is very inconsistent. Here's the code:

$product = Product::find(1);

echo $product->numberOfOrders('2014-05-22', '2015-01-27');

Here's the code for the numberOfOrders method:

public function numberOfOrders($startDate = NULL, $endDate = NULL)
{
        if(is_null($startDate)) $startDate = '0000-00-00 00:00:00';
        if(is_null($endDate)) $endDate = date("Y-m-d");

        return Order::whereHas('orderProducts', function($query)
        {
                $query->where('product_id', $this->id);

        })->whereHas('status', function($query)
        {
                $query->where('sales_data', 1);

        })->where('ext_created_at', '>=', $startDate)->where('ext_created_at', '<=', $endDate.' 23:59:59')->distinct()->count();
}

When this code runs, it hangs for about 10 seconds, before returning the integer result. Then, if I run the same code again, it instantly returns the result and continues to do so every time I run it. If I change the $startDate, and run the code again, it hangs for 10 seconds, then returns the result. Then, rerunning the code will again instantly return the result.

Other times, when the code is hanging, instead of returning a result, I get an error message:

PHP Fatal error:  Uncaught exception 'PDOException' with message 'SQLSTATE[HY000]: General error: 2027 Malformed packet' in /var/www/html/app/vendor/laravel/framework/src/Illuminate/Database/Connection.php:299

After the error message, if I run the code again, the result is returned instantly.

I am very confused as to what would be causing such inconsistency in my code. I am not doing anything with caching.

I noticed that the whereHas method actually adds subqueries to the query rather than using joins, which I find strange. Writing similar code in CodeIgniter using Datamapper ORM adds joins to the query rather than subqueries, which yields much better performance. Could the subqueries be causing the problem? If so, I still am unsure why things would be behaving so inconsistently.

I've captured some data showing the issue occurring. Here's the output from DB::getQueryLog() the first time:

[3] => Array
    (
        [query] => select count(*) as aggregate from `orders` where `orders`.`deleted_at` is null and (select count(*) from `order_products` where `order_products`.`order_id` = `orders`.`id` and `product_id` = ? and `order_products`.`deleted_at` is null) >= 1 and (select count(*) from `order_statuses` where `orders`.`order_status_id` = `order_statuses`.`id` and `sales_data` = ? and `order_statuses`.`deleted_at` is null) >= 1 and `ext_created_at` >= ? and `ext_created_at` <= ?
        [bindings] => Array
            (
                [0] => 1
                [1] => 1
                [2] => 2014-07-29
                [3] => 2015-01-27 23:59:59
            )

        [time] => 22391.73
    )

As you can see, it took a very long time to run. After that was done, I ran the same thing again, and here's the output:

[3] => Array
    (
        [query] => select count(*) as aggregate from `orders` where `orders`.`deleted_at` is null and (select count(*) from `order_products` where `order_products`.`order_id` = `orders`.`id` and `product_id` = ? and `order_products`.`deleted_at` is null) >= 1 and (select count(*) from `order_statuses` where `orders`.`order_status_id` = `order_statuses`.`id` and `sales_data` = ? and `order_statuses`.`deleted_at` is null) >= 1 and `ext_created_at` >= ? and `ext_created_at` <= ?
        [bindings] => Array
            (
                [0] => 1
                [1] => 1
                [2] => 2014-07-29
                [3] => 2015-01-27 23:59:59
            )

        [time] => 22391.73
    )

[4] => Array
    (
        [query] => select count(*) as aggregate from `orders` where `orders`.`deleted_at` is null and (select count(*) from `order_products` where `order_products`.`order_id` = `orders`.`id` and `product_id` = ? and `order_products`.`deleted_at` is null) >= 1 and (select count(*) from `order_statuses` where `orders`.`order_status_id` = `order_statuses`.`id` and `sales_data` = ? and `order_statuses`.`deleted_at` is null) >= 1 and `ext_created_at` >= ? and `ext_created_at` <= ?
        [bindings] => Array
            (
                [0] => 1
                [1] => 1
                [2] => 2014-07-29
                [3] => 2015-01-27 23:59:59
            )

        [time] => 1.81
    )

As you can see, same exact query, but it's drastically faster. Why would there be such a difference? Are the results being cached somewhere?

In case it helps, I'm running all of this code using php artisan tinker.

EDIT After some more research, I've learned that this is probably the behavior of the mysql query cache.

flyingL123
  • 7,686
  • 11
  • 66
  • 135

1 Answers1

0

Your queries are awfully slow. Make sure your tables are properly indexed.

Also, cache your queries, if they are being run pretty often.

Eloquent has a nifty remember() method.

It will use your current cache driver to store the query's results. Redis is doing an awesome job for me so far.

Mysteryos
  • 5,581
  • 2
  • 32
  • 52
  • I believe the tables are indexed properly. They each have an incrementing `id` column and were created with Laravel's Schema Builder. Is there anything else I would have to do to have them "properly indexed"? Cache won't really do much for me here. I need to get the query time down on the first run. When I rewrite the query using joins, it's extremely fast. I don't understand why Laravel uses subqueries instead of joins. Do you? – flyingL123 Jan 28 '15 at 13:28
  • Having an `id` column is not equal to having indexes. If you were to have an index on your primary and foreign keys, the queries would run just as fast as using joins. [This website explains it all](http://www.tizag.com/mysqlTutorial/mysql-index.php) And [link to someone with the same issue on laravel's github](https://github.com/laravel/framework/issues/3543) – Mysteryos Jan 28 '15 at 13:40
  • Got it. Adding indexes made a big difference. The query time for the subquery version is now averaging around 0.10 sec. The join version still runs much faster at 0.01 sec. Is this expected? – flyingL123 Jan 28 '15 at 13:55
  • Very much so. Joins will always be faster than subqueries: [stackoverflow related question](http://stackoverflow.com/questions/2577174/join-vs-sub-query) – Mysteryos Jan 28 '15 at 15:30