کاری که ما به طور معمول برای دریافت دیتا از دیتابیس انجام می دهیم به صورت زیر است:
با استفاده از eloquent
1$products = Product::all();
با استفاده از query builder
1$products = DB::table('products')->get();
در این روش ابتدا کل دیتای جدول از دیتابیس گرفته می شود و سپس در حافظه ذخیره می شود و بعد از پردازش ،نتیجه آن در حافظه نگهداری می شود.
اما ایرادی که در این روش وجود دارد این است که اگر تعداد سطر های جدول شما زیاد باشد( مثلا 2 میلیون ) حافظه سرور شما در کسری از ثانیه پر شده و سرور شما دچار اختلال می شود.
هنگام دریافت داده های بزرگ در لاراول باید دیتا را به قسمت های کوچک تقسیم کرده پردازش را روی آن بخش کوچک انجام داده و سپس بخش جدیدی واکشی شود(fetch).
برای جلوگیری از این اشکال می توان چند راهکار ارایه داد:
1234567$products = Product::chunk(100, function($products){ // process product here }); //or $products = DB::table('products')->chunk(100, function ($products){ // process product here });
در این روش ما ابتدا 100 سطر از دیتابیس میگیریم پردازش های لازم را روی آن انجام می دهیم و سپس 100 سطر دیگر میگیریم و این کار را تا آخر ادامه میدهیم.
توجه داشته باشید در این روش تعداد کوئری ها به دیتابیس زیاد می شود .
اما در عوض حافظه سرور مدیریت می شود.
تعداد row های دریافتی بستگی به تعداد فیلد های دیتابیس و حجم دیتابیس و البته حافظه (RAM) سرور شما دارد.
. استفاده از cursor
12345678foreach(Product->cursor() as $product){ //process a single product } //or foreach(DB::table('products')->cursor() as $product){ //process a single product }
دقیقا برعکس روش قبل که طی چندین کوئری دیتا را از دیتابیس واکشی می کرد در این روش ما تمامی اطلاعات را در قالب یک کوئری از دیتابیس دریافت می کنیم اما سطر ها را یکی یکی وارد مدل لارول (Eloquent model) می کنیم که باعث بهینه شدن استفاده لارول از حافظه می شود.
از این روش زمانی استفاده کنید که دچار کمبود حافظه در سرور لارول شده اید و حافظ ای جداگانه برای دیتابیس خود تخصیص داده اید اگه حافظه دیتابیس شما با حافظه اپلیکیشن یکی باشد ، این روش به کار شما نمی آید و باز دچار کمبود حافظه می شوید.
. استفاده از chunkById
12345$products= Product::chunkById(100, function($products){ }); //or $products= DB::table('products')->chunkById(100, function ($products){ });
تفاوتی که chunkById با chunk در نحوه تولید SQL خروجی می باشد.
1select * from products offset 0 limit 100
1select * from products where id > 100 order by id asc limit 100
در روش chunk با استفاده از offset پست ها تقسیم می شوند که روش بسیار کندی است و ما همواره سعی می کنیم تا جایی امکان دارد از offset استفاده نکنیم.
اما chunkById با استفاده از where
و جستجو در فیلد (id
) 100 سطر را نتخاب می کند.
دقت داشته باشید که فیلد id باید primary key
و auto increment
باشد.
به طور معمول زمانی که می خواهیم یک سطر از دیتابیس را واکشی کنیم از این روش استفاده می کنیم
123$products= Product::find(1); //or $products= DB::table('products')->where('id','=',1)->first();
در این روش ما فارغ از این که فقط به یک فیلد (برای مثال نام محصول ) نیاز داریم تمامی ستون های جدول را دریافت می کنیم که باعث کند شدن اجرای کوئری و مصرف بیشتر حافظه ما می شود.
استفاده از select
123$products= Product::select([' id' , 'name' ])->find(1); //or $products= DB::table('products')->where('id','=',1)->select(['id' , 'name' ])->first();
اما با استفاده از select می توانیم تنها ستون هایی که نیاز داریم را واکشی کنیم.
این روش به طور کلی ربطی به کوئری گیری از دیتابیس ندارد و تمرکزش بر کاهش زمان پردازش بعد از دریافت دیتا از دیتابیس است.
همان طور که در نکته بالا گفتم برای محدود کردن تعداد ستون های دریافتی از select
استفاده می کنیم.
اما روشی که لاراول برای آن انجام می دهد به صورت زیر است:
select id,name from products where id = 1 limit 1
اجرا و اطلاعات از دیتابیس گرفته می شود.Product model
ساخته می شود.Product model
ساخته شده بود در یک Collection
قرار می گیرد.Collection
برگردانده می شد.id
و name
به صورت زیر عمل می کنیم:1234foreach ($products as $product){ $product->id; $product->name; }
اما ما در این حالت فقط نیاز به گرفتن 2 فیلد از دیتابیس داریم و در عمل به instance ی که از مدل Product
برای ما ساخته شده است نیازی نداریم.
پس می توانیم با استفاده از pluck به راحتی و با کمترین میزان پردازش به اطلاعت مورد نیاز دسترسی پیدا کنیم.
123$products = Product::pluck('name', 'id'); //or $products = DB::table('products ')->pluck('name','id');
در این روش :
select id,name from
products where id = 1 limit 1
اجرا و اطلاعات از دیتابیس گرفته می شود.id
و مقدار آن (value) name
است ساخته می شود.1234foreach ($products as $id=> $name){ // $id is the title of a product // $name is the slug of a product }
دقت داشته باشید در این روش مدل ساخته نشده و سرعت پردازش و مصرف حافظه بعد از دریافت دیتا از دیتابیس بهینه شده است.
به طور معمول زمانی که می خواهیم تعداد سطر ها را بشماریم از روش زیر استفاده میکنیم:
123$products= Product::all()->count(); //or $products= DB::table('products')->get()->count();
در روش بالا ابتدا با استفاده از کوئری زیر :
select * from products
تمامی سطر ها را از دیتابیس واکشی می شود و سپس آن را در collection لاراول لود کرده و تعداد را شمارش می کند .
استفاده از این روش برای شمارش هنگامی که حجم جدول پایین است مشکلی ندارد اما زمانی که حجم بالا باشد به سادگی دچار کمبود حافظه می شویم.
راهکار استفاده از روش زیر است :
123$products= Product::count(); //or $products= DB::table('products')->count();
لاراول برای اجرای روش بالا ، کوئری زیر را اجرا میکند :
select count(*) from products
که روش استانداردی برای شمارش است.
توجه داشته باشید که به طور کلی عمل شمارش سطر ها (count) در SQL نیاز به پردازش زیادی دارد پس تا جای امکان سعی کنید از count استفاده نکنید.
مبحث لود کردن n+1 کوئری که بسیار مهم و در صورت رعایت نشدن می تواند تاثیر چشم گیری بر روی سرعت داشته باشد.
به علت گسترده بودن این مبحث در این مطلب توضیح داده نمی شود شما می توانید با یک جستجو ساده مقالاتی در این زمینه پیدا کنید.
ما معمولا زمانی که میخواهیم یه کلمه را در جدول پیدا کنیم از روش زیر استفاده میکنیم:
select * from table_name where column like %keyword%
این کوئری کل جدول ما را به طور کامل جستجو می کند. اما اگر بدانیم که عبارت کلیدی ما فقط در ابتدای جمله ها یافت می شود می توان از کوئری زیر استفاده کرد که بسیار سریع تر است.
select * from table_name where column like keyword%
به طور کلی استفاده از توابع SQL در where موجب اسکن کل جدول و پردازش بیشتری می شود و باید تا جای امکان سعی کنید از آن استفاده نکنید.
برای مثال ما معمولا برای دریافت دیتا براساس تاریخ از روش زیر استفاده می کنیم:
1$products= Product::whereDate('created_at', '>=', now() )->get();
که کوئری که لارول در بک گراند اجرا می کند به صورت زیر است:
select * from products where date(created_at) >= 'timestamp-here'
که همان طور که مشاهده می کنید از از تابع date استفاده شده است در صورتی که می توانستیم از روش زیر استفاده کنیم:
1$products = Product::where('created_at', '>=', now() )->get();
که کوئری خروجی آن با استفاده از where می باشد.
select * from products where created_at >= 'timestamp-here'
یکی از اشتباهاتی که برنامه نویسان هنگام طراحی پایگاه داده انجام می دهند اضافه کردن تعداد بسیار زیادی ستون (Column) به یک جدول است که باعث افزایش حجم دیتای آن جدول می شود .
که به خصوص در MYQSL این کندی بیشتر هم هست.
راهکار تقسیم ستون ها در جدول های دیگر و جوین(JOIN) کردن آن هنگام واکشی اطلاعت است.اما باید به این نکته توجه شود که تا جای امکان ستون هایی که وابستگی کمتری نسبت به یکدیگر دارند از هم جدا شوند که نیاز به جوین کردن جدول ها تا جای امکان کاهش یابد.
برای مثال نام و نام خانوادگی دو فیلدی هستند که وابستگی به یکدگیر دارند و هرجا نیاز به استفاده از "نام"باشد "نام خانوادگی" نیز نیاز است پس اگر این دو فیلد از یکدیگر جداشوند شما همواره نیاز به جوین کردن جدول ها را دارید و این روش عملا به کار شما نمی آید.
به طور معمول ما برای گرفتن آخرین رکورد ها ( از آخر به اول ) از دیتابیس از روش زیر استفاده می کنیم:
123$products= Product::latest()->get(); // or $products= Product::orderBy('created_at', 'desc')->get();
که در این روش لاراول کوئری زیر را تولید و اجرا می کند:
select * from products order by created_at desc
همان طور که مشاهده می کنید در این کوئری ابتدا محصولات بر اساس فیلد created_at
مرتب شده و در نهایت آخرین رکورد انتخاب می شود.
اما باید به این نکته دقت داشته باشید که created_at
یک داده متنی می باشد و مرتب سازی آن به مراتب از مرتب سازی اعداد کند تر می باشد.
اما اگر شما فیلد id
که auto increment باشد داشته باشید می توانید مرتب سازی را بر اساس id
انجام دهید که روش بسیار سریع تری می باشد.
123$products= Product::latest('id')->get(); // or $products= Product::orderBy('id', 'desc')->get();
سخن پایانی :
به طور کلی بهینه سازی یک مسئله وابسته به ساختار دیتابیس و کد شماست و باید روش ها نسبت به اپلیکشن شما شخصی سازی شود .
شما می توانید با نصب ابزار laravel debugbar کوئری های اجرایی ، میزان مصرف حافظه و زمان پاسخ اپلیکشن خود را سنجیده و با توجه به آن بهینه سازی لازم را انجام دهید.