Tối ưu laravel
Mình có 1 database daily ( id, affID, nguoigt,name ... ) Bài toán là khi vào 1 user bất kì sẽ load ra tất cả các tuyến dưới mà họ đã giới thiệu ra. ( mình đã làm được rồi nhưng cảm thấy truy vấn quá nhiều có vẻ chưa tối ưu)
Model:
public function childrenCategories()
{
return $this->hasMany('App\DaiLy', 'nguoigt', 'affID')->with('childrenCategories')->select('id','affID','nguoigt');
}
Controller:
$daily_x = DaiLy::where('affID',session('affID'))->with('childrenCategories')->select('id','affID','nguoigt')->get()->toArray();
$collection = collect($daily_x);
$collapsed = $collection->flatten();
$arr = array();
foreach($collapsed as $key=>$v) { if($key%3 ==0) { $arr[]=$v; } }
$dailyChild = DaiLy::where('id','!=',1)->whereIn('id',$arr)->select('id','affID','nguoigt','level','name')->Orderby('created_at','DESC')->with('tuyentren')->Simplepaginate(50);
return view('home',compact('dailyChild','user'));
Và Kết quả:
Cho mình hỏi có cách nào xử lý tốt hơn không?
6 CÂU TRẢ LỜI
Muốn load tất cả các cấp dùng relationship dạng parent_id sẽ bị như này vì nó đệ quy. Chuyển qua nested set hoặc bạn đang dùng mysql 8 thì có hỗ trợ query đệ quy đó.
nếu xây dựng từ đầu thì ok, nhưng bây giờ DB đã có rồi ko thay đổi hoàn toàn như vậy được nữa bạn à.
@tinhtn89 theo mình thì chỉ có làm như trên thôi, hoặc load 1 cấp con đầu tiên, dùng mấy cái plugin tree bấm mở tree thì ajax load thêm mấy thằng đệ ra kiểu này. Chứ load 1 lượt con cháu chắt ra hết thì chắc k có cách nào ngoài cái đệ quy lặp query
mình hiểu ý bạn nhưng ko được, phải load hết ra thàng dạng bảng phân trang cơ. mà dữ liệu ko ít nó là nhiều nghìn cơ.
public function childrenCategories()
{
return $this->hasMany('App\DaiLy', 'nguoigt', 'affID')->with('childrenCategories')->select('id','affID','nguoigt');
}
Có thể đoạn đệ quy này gây ra việc tốn nhiều query đấy
đúng là nó, nhưng ko biết dùng cách nào thì tốt hơn ko?
Bạn có thể thử truy vấn với WITH RECURSIVE (sử dụng CTE) để truy vấn mọi đại lý cấp dưới, và cấp dưới của cấp dưới,... của 1 user.
WITH RECURSIVE a AS (
SELECT *, 0 AS level, CAST(affID AS VARCHAR) AS path
FROM daily
WHERE nguoigt = 0
UNION ALL
SELECT b.*, a.level + 1 AS level, CAST(CONCAT(a.path, '/', b.affID) AS VARCHAR) AS path
FROM a
INNER JOIN daily b
ON a.affID = b.nguoigt
)
SELECT * FROM a ORDER BY path;
Đây là dữ liệu giả + ví dụ: http://sqlfiddle.com/#!17/e8210f/4
CTE hỗ trợ cho mariaDB từ bản 10.2.2 trở lên nên bạn cần phải update phiên bản mariaDB mới có thể gọi câu query trên.
Mình chưa nhìn được toàn bộ code nên cũng chưa biết có chỗ nào trong code gây n+1 query nữa không nhưng hiện tại theo code trên thì có đoạn khai báo relations:
public function childrenCategories()
{
return $this->hasMany('App\DaiLy', 'nguoigt', 'affID')
->with('childrenCategories')
->select('id','affID','nguoigt');
}
Khi code Laravel bạn nên lưu ý không dùng query sql hoặc eager loading trong getter / accessor, hoặc trong các class có nhiệm vụ transform dữ liệu trước khi response... nó sẽ là nguyên nhân gây ra vấn đề n+1 query. Bạn nên bỏ cái with('childrenCategories')
này đi.
Mình đọc comment phía dưới, có vẻ bạn muốn truy vấn danh sách danh mục đa cấp. Bạn thử chuyển đổi code qua các cách dưới đây xem sao nhé:
- Cách 1: Trả về danh sách danh mục gốc, Web UI sẽ có nút chức năng tải chi tiết các danh mục con của danh mục tương ứng. Khi click vào thì bạn sẽ gọi api để lấy ra danh các danh mục con.
- Cách 2: Thêm một trường danh string để lưu đường dẫn đa cấp của một danh mục
nested_path
- là chuỗi các ID của danh mục mà nó kế thừa (id của danh mục cha). VD:
__ ID 1 - Category 1=> nested_path = null
|__ ID 3 - Category 1.1 => nested_path = "1"
| |__ ID 5 - Category 1.1.1 => nested_path = "1_3"
| |__ ID 6 - Category 1.1.2 => nested_path = "1_3"
|
|__ ID 4 - Category 1.2 => nested_path = "1"
Khi muốn lấy danh mục con của Category 1, bạn sử dụng regex trong SQL để query nested_path ^= "1_", khi đó chỉ mất 1 truy vấn có thể lấy được toàn bộ children, bây giờ dùng code để sắp xếp nó thành dạng đệ quy nếu bạn cần. Do ít query nên nó cũng sẽ nhanh hơn đó.
Bạn tham khảo nhé.
Cảm ơn bạn, ý tưởng rất hay.
433 queries thì rõ ràng là không được r. Ở bên phía view bạn có gọi ra quan hệ nào của nó mà chưa load ra k?
View chỉ show các trường ra thôi, ko có gì cả .
Nếu dữ liệu cây của bạn dưới 1000 item thì bạn lấy hết tất cả rồi xử lý bằng thuật toán thì chỉ cần 1 câu query.
cách này chắc là tối ưu nhất về mặt hiệu năng:
- Select all từ bảng daily
- Ở php build tree bằng recursive Nếu bảng daily to quá thì cân nhắc dùng chuck để buffer ko bị tràn Cách này bị dư thừa ở chỗ lấy all bảng daily nhưng sẽ tránh đc việc phải query nhiều
Bạn chụp cả code bên view cho mn cũng xem để dễ hình dung hơn ấy
View thì cũng ko có gì đáng nói cả chỉ là show các trường thông tin lên thôi bạn à.
Bạn nói rõ hơn về các trường của bảng đại lý kia (id, affID, nguoigt,name) về kiểu dữ liệu & mục đích được không, mình đang chưa hiểu rõ lắm . Với version mySQL bạn đang dùng là version mấy?
đây là dự án xây dựng hệ thống đa cấp ( hiểu là menu đa cấp cũng được) ID: AffID: là mã của mỗi người nguoigt: Người giới thiệu (có thể hiểu là parent_id) name: tên còn lại các trường khác ko cần quan tâm.