Yêu cầu Aug 12th, 2020 3:56 a.m. 441 1 6
  • 441 1 6
+1

Tối ưu laravel

Chia sẻ
  • 441 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

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>

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?

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

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

6 CÂU TRẢ LỜI


Đã trả lời 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 đó.

Chia sẻ
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 à.

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

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

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.

Đã trả lời 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

Chia sẻ
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?

Đã trả lời 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.

Chia sẻ
Đã trả lời 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é.

Chia sẻ
Avatar TinhTN @tinhtn89
Aug 18th, 2020 8:02 a.m.

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

Đã trả lời 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?

Chia sẻ
Avatar TinhTN @tinhtn89
Aug 12th, 2020 8:03 a.m.

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

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.

Đã trả lời 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
Chia sẻ
Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí