Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

inyansuta's avatar

Eloquent is so slow ...

I have a table of products, hundreds of thousands of records, and I want to get eg. 10,000 specific products (cron, export).

Note: The example below is already final sql query, I missed everything like $products = Product::with('prices')->get() ...

$product_ids = [3, 8, 11, ...]; // total of 10,000 elements in array

If you run a simple query to classic mysql connection:

$query = mysql_query("SELECT * FROM products WHERE id IN (" . implode($product_ids, ',') . ")");
$products = [];
while($item = mysql_fetch_object($query)) {
    $products[] = $item;
}

query runs for 0.1 seconds.

If the same query I will send over their own pdo:

$stmt = $myPdo->query($query);
$data = $myPdo->fetchAll();

the query will be even faster.

However, the same question over Eloquent lasts 1 minute, which is 600 times slower! I got to the eloquent class Connection, and found that the problem is in the way Eloquent triggers pdo:

$statement = $this->getPdoForSelect($useReadPdo)->prepare ($query);
$statement->execute($me->prepareBindings($bindings));
$data = $statement->fetchAll (); // here is big problem ...

So the difference in speed and thus the problem does prepare > execute > fetchAll ...

I'm doing something wrong, or to such queries only solution is to use your own raw sql query?

0 likes
25 replies
bobbybouwmann's avatar

You probably have something wrong in the query your created with Eloquent because Eloquent is really fast, I have millions of records and I don't have to wait longer the 2 seconds... Never

inyansuta's avatar

No, i am sure, try eloquent query with "where id IN ( [array with 10.000 ids] )".

janareit's avatar

@inyansuta I tested L5 before starting to learn Laravel further and I db:seed-ed 1,5 million blog posts with 1,5 million comments on them to test Laravel speed. I didn't do any modifications to my install and used simple MySQL InnoDB. Normal Eloquent queries with pagination to pages took ~600-800ms. So it must be something with your script...

1 like
inyansuta's avatar

Mayby yes... This is what I try:

Module Product (table products has 35894 rows):

public function prices()
   {
      return $this->hasMany('Prices');
   }

This query = 30 seconds

Product::select(['id'])->with('prices')->take(10000)->get();

If i log all Eloquent queries - here is sql queries output:

{
"query": "select `id` from `products` limit 10000",
"bindings": []
},
{
"query": "select * from `prices` where `prices`.`product_id` in (?, ?, ...,)", // 10.000x "?"
"bindings": [2, 8, ...] // 10.000x "id"
}

And returned data (38.486 rows):

{
"id": "1",
    "prices": [
    {
        "id": "165270",
        "product_id": "1",
        "price_group_id": "2",
        "price": "68.55"
    },
    {
        "id": "165270",
        "product_id": "1",
        "price_group_id": "3",
        "price": "52.55"
    }
    ]
},
{
"id": "2",
    "prices": [
    {
....

What is wrong?

And if I debug this in eloquent Connection class (i wrote in first topic), i am shure, that problem is here:

$statement = $this->getPdoForSelect($useReadPdo)->prepare ($query);
$statement->execute($me->prepareBindings($bindings));
$data = $statement->fetchAll (); // here is problem - this query takes 30 seconds

If at this point in the class Connection perform debug, and those same questions that are performed in the Eloquent done using their own pdo connections without using pdo prepare - execute - then the second part will take place in 0.1 seconds ...

// class Connection, original code
$statement = $this->getPdoForSelect($useReadPdo)->prepare ($query);
$statement->execute($me->prepareBindings($bindings));
$data = $statement->fetchAll (); //takes 30 seconds

// just added my code below
$dbh = new PDO('mysql:dbname=' . DB_DATABASE . ';dbhost=' . DB_HOST, DB_USER, '');
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $dbh->query(the_some_sql_query_with_10000_in_IN_without_prepare_and_execute);
$data = $stmt->fetchAll(); // takes 0.1 second
pmall's avatar

Sure it is slow, first it fetch all the record in one time, then it instantiate an eloquent object for each lines. It is not made to retrieve 10 000 objects.

Also, please be sure you didn't forgot an index on prices.product_id ...

However you can try to do something with chunk :

If you need to process a lot (thousands) of Eloquent records, using the chunk command will allow you to do without eating all of your RAM:

User::chunk(200, function($users)
{
    foreach ($users as $user)
    {
        //
    }
});
2 likes
inyansuta's avatar

Can someone please explain?

Eloquent - original code, class Connection / method select()

$bindings = ["32", "54", "67, ..."]; // 10.000 id in array
$query = "select * from `prices` where `prices`.`product_id` in (?, ?, ?)"

$statement = $this->getPdoForSelect($useReadPdo)->prepare($query);
$statement->execute($me->prepareBindings($bindings));
$data = $statement->fetchAll(); // - this is very SLOW (30 seconds)

Eloquent - my testing modification of the original code above

$bindings = ["32", "54", "67, ..."]; // 10.000 id in array
$query = "select * from `prices` where `prices`.`product_id` in (" . implode(',', $bindings) . ")"

$statement = $this->getPdoForSelect($useReadPdo)->query($query);
$data = $statement->fetchAll(); // - this is very FAST (0.1 seconds)

I love Eloquent, but throughout the day can not identify the cause.

Please help me, i dont want use the "old wy " with "select * from products left join prices ..." (this is fast without problem), but i want use Product::with('prices')... but i not understand problem above...

acasar's avatar

@inyansuta The query executed by Eloquent runs equally fast as plain PDO. The problem occurs after that - Eloquent takes the results of the query (approx. 38000 rows) and converts each row into an object. So basically it creates 38000 objects, which is slow. When dealing with that amount of results, you should really use the query builder:

$results = DB::select( DB::raw($query), $bindings );

This will return a plain array and will be much faster.

RemiC's avatar

@inyansuta : from what I see in your test, the slowness comes from prepared statement, not Eloquent itself. Such difference seems, indeed, anormal to me.

inyansuta's avatar

@anzze Yes, but if I'm not mistaken Eloquent creates individual objects after the end of the line:

return $statement->fetchAll($me->getFetchMode());

Yes, transferring huge amounts of records to objects not solved yet, respectively. the subsequent conversion anymore so long ... The problem is that just above line takes so long (in case it is used in large quantities id). I do not understand that. Eloquent sets or somewhere something else starts with each pass fetchAll native PDO? I do not think it is possible, moreover, I searched and found nothing of the sort. Thanks for the additional tips and insights to the above problem.

marcoacm's avatar

What was the solution for this? I have the problem... Please let me know how you fixed it, I can't follow the thread very well.

tylernathanreed's avatar

You could also use ->toSql() before calling the Eloquent Query to see what it resolves to.

pmall's avatar

This is not related to sql at all. Creating 10,000 eloquent object at once is very slow, thats all. You should not use eloquent for batch treatments like this.

hthuong09's avatar

Sorry for bumping the topic. I just want to give a simple solution for anyone who encountered the same problem with me and topic creator.

As many discussion posts above. The reason make query so slow is because of Eloquent creating object for each parameter before binding. The suggested solution was use Query Builder for such a task.

But for me I don't want to sacrifice the benefits of using Eloquent. Therefore, I try another solution. Using whereRaw instead of whereIn.

The solution is very simple. You can use Eloquent normally, but for whereIn which has lots of parameters. Use whereRaw instead. For example:

$query = Task::with(['creator', 'assignee'])
$queryIn = sprintf("creator_id IN ('%s')", implode("', '", $creatorIds));
$query->whereRaw($queryIn);
$query->get();
1 like
36864's avatar

That's a pretty neat way of building a SQL injection vulnerability into otherwise perfectly secure code.

1 like
leonrenkema's avatar

I stumbled upon the same issue and and implode of all ids is the solution to make it quick. SQL injection vulnerability depends on the source of the array, if it is a clean array of int's there will be no problem.

1 like
bluenestbryan's avatar

This still seems to be a problem, I debugged all the way down to fetchAll() and then found this thread. This occurred in the same ways others have mentioned - I am using a whereIn() query with 30,000+ entries for the "in" array.

I found this somewhat relevant Stackoverflow question below, which in turn references a PHP bug report in the comments. After reading the bug report... it sounds like this is confirmed as an issue in PHP's PDO library.

https://stackoverflow.com/questions/4350718/why-are-certain-types-of-prepared-queries-using-pdo-in-php-with-mysql-slow

https://bugs.php.net/bug.php?id=53458

For now I am using the whereRaw() workaround proposed in this thread, which resolved my issue (while sacrificing the benefits of using prepared statements with bindings).

jlrdw's avatar

If someone is saying they can import or export a million records in under a second, even under 10 seconds, Bull.

Even chunking it will take a while.

Even raw mysql with 500,000 will take at least a minute.

And eloquent is not that great for large amounts of data.

As example, a large report with inner foreach:

parent record a 
-------- child 1
-------- child 2
-------- child 3

------------------------
parent record b 
-------- child 1
-------- child 2
-------- child 3
-------- child n
--------    .
--------    .
--------    .
-------- child 100,000

Top parent = 3 child records, eloquent fine. Next record has 100,000 eloquent probably tuck tail and squeal.

Active record is not good for large results.

in other words these nested foreach loops with thousands of records in inner foreach, well you see the point.

A few good articles:

https://laracasts.com/discuss/channels/eloquent/writing-all-queries-directly-vs-model-relations

https://laracasts.com/discuss/channels/laravel/sql-native-to-query-builder

https://laracasts.com/discuss/channels/laravel/coverting-ms-access-queries-to-laravel-query-builder

https://laracasts.com/discuss/channels/general-discussion/sql-injection-2

Not laravel, but yii - still a good read.

https://www.yiiframework.com/wiki/2541/when-to-use-active-record

siangboon's avatar

indexing of right column also make the big different too..

Protopia's avatar

I know this is a late addition, but an alternative is to do:

$query = Task::with(['creator', 'assignee'])
$queryIn = sprintf("creator_id IN ('%s')", implode("', '", $creatorIds));
$query->whereRaw($queryIn);
$query->toBase()->get();

There does not appear to be a lot of documentation for this but apparently it results in an array or BaseCollection rather than turning each into an object.

Sinnbeck's avatar

@Protopia This is 2 years old, but no need to use that sort of raw. Laravel supports it out of the box

$query->whereIntegerInRaw('creator_id', $creatorIds);
Rasysonlu's avatar

@Protopia Use whereRaw() , which like to use PDO query(), will fast in fetchAll(), but will slow in sql query.(2x slow for PDO bindValues())

@sinnbeck To use whereIntegerInRaw() , it is same to use PDO query() or prepare()+execute() ?

I want to know is the pdo bug that @bluenestbryan say have been fix?

Please or to participate in this conversation.