Asked Aug 12th, 2020 3:56 a.m. 444 1 6
  • 444 1 6
+1

Tối ưu laravel

Share
  • 444 1 6

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?

Avatar Phạm Tuấn Anh @phamtuananh760
Aug 12th, 2020 4:22 a.m.

Bạn chụp cả code bên view cho mn cũng xem để dễ hình dung hơn ấy

0
| Reply
Share
Avatar TinhTN @tinhtn89
Aug 12th, 2020 7:52 a.m.

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 à.


<table class="table table-striped" id="myTable">
	<tr class="alert-link header">
  		<td>STT</td>
  		<td>SHĐ</td>
  		<td>Tên</td>
  		<td>Địa chỉ</td>
  		<td>Tổng điểm</td>
  		<td>Trang Thái</td>
  	</tr>	
	@if($dailyChild)
		@foreach($dailyChild as $stt=> $item)
		<tr class="even gradeC" align="center">
        <td>{{$stt}}</td>
        <td>{{$item->affID}}</td>
        <td class="oname">{{ $item->name}}</td>
        <td>{{$item->diachi}}</td>
<td>{{$item->commission}}</td>
<td>
@if($item->state == 1)  
<span class=" btnactive btn-success btn"><i class="fas fa-check-square"></i></span>
@else
<span class=" btnactive btn-danger btn"><i class="fas fa-check-square"></i></span>
@endif

</td>
</tr>
	@endforeach
	@endif
</table>

<div class="pagination">{{$dailyChild->links()}}</div>

0
| Reply
Share
Avatar Trần Xuân Thắng @tranxuanthang
Aug 12th, 2020 8:21 a.m.

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?

0
| Reply
Share
Avatar TinhTN @tinhtn89
Aug 12th, 2020 8:35 a.m.

đâ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.

31.JPG

0
| Reply
Share
Avatar Trần Xuân Thắng @tranxuanthang
Aug 14th, 2020 2:45 a.m.

6 ANSWERS


Answered Aug 12th, 2020 5:43 a.m.
+2

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 đó.

Share
Avatar TinhTN @tinhtn89
Aug 12th, 2020 8:05 a.m.

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 à.

0
| Reply
Share
Avatar Phạm Thảo @cr_ronaldo16396
Aug 12th, 2020 9:51 a.m.

@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 1.png

0
| Reply
Share
Avatar TinhTN @tinhtn89
Aug 14th, 2020 2:35 a.m.

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ơ.

0
| Reply
Share
Avatar Phạm Thảo @cr_ronaldo16396
Aug 14th, 2020 8:35 a.m.

@tinhtn89 vậy chỉ có nâng cấp mysql hoặc mariadb lên tới version mà hỗ trợ query đệ quy thôi.

0
| Reply
Share
Answered Aug 12th, 2020 4:24 a.m.
+1
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

Share
Avatar TinhTN @tinhtn89
Aug 12th, 2020 8:04 a.m.

đúng là nó, nhưng ko biết dùng cách nào thì tốt hơn ko?

0
| Reply
Share
Answered Aug 14th, 2020 3:09 a.m.
+1

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.

Share
Answered Aug 16th, 2020 12:28 p.m.
+1

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é.

Share
Avatar TinhTN @tinhtn89
Aug 18th, 2020 8:02 a.m.

Cảm ơn bạn, ý tưởng rất hay.

0
| Reply
Share
Answered Aug 12th, 2020 4:06 a.m.
0

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?

Share
Avatar TinhTN @tinhtn89
Aug 12th, 2020 8:03 a.m.

View chỉ show các trường ra thôi, ko có gì cả .

0
| Reply
Share
Avatar Tuan Bach Van @bachvtuan
Aug 13th, 2020 1:28 a.m.

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.

0
| Reply
Share
Answered Aug 14th, 2020 3:41 a.m.
0

cách này chắc là tối ưu nhất về mặt hiệu năng:

  1. Select all từ bảng daily
  2. Ở 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
Share
Viblo
Let's register a Viblo Account to get more interesting posts.