lazy() لاراول به اندازه chunk() کنده
خلاصهٔ کاملتر
نویسنده یه جدول MySQL با یک میلیون سفارش ساخته و همهی ردیفها رو با پنج روش پیمایش لاراول خونده: chunk()، chunkById()، lazy()، lazyById() و cursor(). به گفتهی خودش chunkById(1000) تو 21.9 ثانیه و lazyById(1000) تو 22.3 ثانیه تموم شدن و هیچکدوم بالای 5.9 مگابایت حافظهی PHP نگرفتن. chunk() و lazy() همونقدر کم حافظه گرفتن ولی حدود شش برابر طول کشیدن، و cursor() با 63.8 ثانیه و 100.4 مگابایت از هر دوی متدهای ById عقبتر موند.
دلیلش تو سورس فریمورکه: chunk() و lazy() هر صفحه رو با OFFSET میگیرن (یعنی به دیتابیس میگن اول n ردیف رو رد کن، بعدیها رو بده)، ولی chunkById() و lazyById() آخرین id رو یادشون میمونه و ردیفهای بعد از اون رو میخوان. این دو خط، آخرین صفحهای هست که هر استراتژی از دیتابیس خواسته:
select * from `orders` order by `orders`.`id` asc limit 1000 offset 999000
select * from `orders` where `id` > 999000 order by `id` asc limit 1000EXPLAIN ANALYZE روی خط اول نشون میده MySQL یک میلیون ردیف رو میخونه تا هزار تا برگردونه، در حالی که خط دوم فقط هزار ردیف میخونه. تو کل اجرا، کوئریهای chunk() حدود 116 ثانیه از 135 ثانیهش رو خوردن و کوئریهای chunkById() فقط 3.4 ثانیه. همین OFFSET یه تلهی دیگه هم داره: اگه داخل حلقه ردیفها رو آپدیت کنی و شرط کوئری روی همون ستون باشه، ردیفها جا میمونن. روی یه کپی 10 هزار ردیفی، chunk(1000) و lazy(1000) هر کدوم فقط 5 هزار ردیف رو آپدیت کردن، ولی دو متد ById هر 10 هزار ردیف رو گرفتن.
cursor() فقط یه کوئری میزنه و مدلها رو یکییکی میسازه، ولی pdo_mysql بهطور پیشفرض کوئریها رو بافر میکنه (یعنی کل نتیجه همون لحظهی اجرای کوئری کپی میشه تو پروسهی PHP) و همین میشه اون 100.4 مگابایت. بافر رو میتونی روی یه کانکشن جدا خاموش کنی:
'options' => [
\Pdo\Mysql::ATTR_USE_BUFFERED_QUERY => false,
],با این تنظیم مصرف حافظه به 3.3 مگابایت رسید، ولی در عوض تا آخرین ردیف خونده نشه هیچ کوئری دیگهای روی اون کانکشن اجرا نمیشه؛ لود کردن یه ریلیشن یا آپدیت کردن خود مدل وسط حلقه ارور میده. cursor() اصلا eager load هم نمیکنه و with() رو نادیده میگیره. نویسنده دو تلهی خود متدهای ById رو هم میگه: ستونی که باهاش صفحه میگیری باید یکتا و ایندکسدار باشه، و هر orWhere سطحبالا شرط id > رو میبلعه، پس شرطها باید تو یه closure پیچیده شن. انتخاب خودش lazyById() برای تقریبا هر حلقه روی جدول بزرگه، و chunkById() وقتی واحد کار یه بستهست.
نکات کلیدی:
- chunkById(1000) یک میلیون ردیف رو تو 21.9 ثانیه و lazyById(1000) تو 22.3 ثانیه پیمایش کرد، هر دو زیر 6 مگابایت حافظه.
- lazy() زیر پوست مثل chunk() با OFFSET صفحه میگیره، نه مثل chunkById(): 133.7 ثانیه در برابر 22.3 ثانیه.
- cursor() روی MySQL حدود 100.4 مگابایت گرفت؛ با خاموش کردن بافر روی یه کانکشن جدا شد 3.3 مگابایت.
- cursor() eager load نمیکنه: 10001 کوئری برای 10 هزار ردیف، در مقابل 20 کوئری lazyById().
- روی PostgreSQL شمارندههای حافظهی PHP بافر libpq رو نمیبینن: 3.3 مگابایت گزارش شد در حالی که حافظهی واقعی پروسه به 324.8 مگابایت رسید.
- اعداد روی Laravel 13.33.0، PHP 8.5.10، MySQL 8.4.11 و PostgreSQL 18.6 گرفته شدن، پس نسبتها رو ببین نه ثانیهها رو.




