我是廣告,點擊一下吧!
標籤
#Flutter (15) 、 #PHP (9) 、 #Laravel (7) 、 #Dart (5) 、 #MySQL (5) 、 #Mac (4) 、 #VS Code (2) 、 #IDE (2) 、 #List (2) 、 #Android (2) 、 #Carbon (2) 、 #Linux (2) 、 #Shell Script (2) 、 #MySQL 效能 (1) 、 #Pagination (1) 、 #Cursor Pagination (1) 、 #LaTeX (1) 、 #個人空間 (1) 、 #Android Splash Screen (1) 、 #createFromTimestamp (1) 、 #資安 (1) 、 #Google Maps Static API (1) 、 #Mac M1 (1) 、 #floorMonth (1) 、 #subMonthNoOverflow (1) 、 #addMonthNoOverflow (1) 、 #subMonth (1) 、 #addMonth (1) 、 #keytool (1) 、 #Play App Signing (1)mysql> SELECT GREATEST(2,0);
-> 2
mysql> SELECT GREATEST(34.0,3.0,5.0,767.0);
-> 767.0
mysql> SELECT GREATEST('B','A','C');
-> 'C'
mysql> SELECT LEAST(2,0);
-> 0
mysql> SELECT LEAST(34.0,3.0,5.0,767.0);
-> 3.0
mysql> SELECT LEAST('B','A','C');
-> 'A'
在 MySQL 中當資料表筆數過多時,使用 limit N offset M 查詢頁面越後面越久,因為 offset 會把 M 筆資料全都抓出來後再丟棄,造成 I/O 的資源浪費。
SELECT id, name FROM users ORDER BY id LIMIT 10 OFFSET 0 -- 很快
SELECT id, name FROM users ORDER BY id LIMIT 10 OFFSET 10000000 -- 超慢,會撈取 10000010 筆資料再丟棄前面的 10000000
解決方法有兩種,取決於需不需要跳頁(頁碼),如果服務是無限滾動的分頁方式,可以參考 Laravel 的 Cursor Pagination,他會使用主鍵(Primary Key)透過 Index 搜尋加速查詢。
SELECT id, name FROM users id > 10000000 ORDER BY id ASC LIMIT 10
若需要頁碼的話也是需要索引(Index)的幫助,先撈出需要的 id 再透過 WHERE IN 方式撈取。
SELECT id FROM users ORDER BY id LIMIT 10 OFFSET 10000000 -- 很快,因為只有用到 PK,不需回表查詢(Lookup)
SELECT id, name FROM users id IN (
SELECT id FROM users ORDER BY id LIMIT 10 OFFSET 10000000
)
但這麼做會有可能因為 MySQL 設定出現
#1235 - This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
解決方法有兩種
1. 拆成兩個 Query,先將 ids 取出後再 WHERE IN
SELECT id FROM users ORDER BY id LIMIT 10 OFFSET 10000000;
SELECT id, name FROM users WHERE id IN (10000001, 10000002, 10000003, 10000004, 10000005, 10000006, 10000007, 10000008, 10000009, 10000010);
<?php
// Simple Paginate
$ids = collect(Users::query()->select('id')->orderBy('id')->simplePaginate(10)->items())->pluck('id');
// Custom Offset and Limit
$ids = Users::query()->select('id')->orderBy('id')->offset(10000000)->limit(10)->get();
$data = Users::query()->select(['id', 'name'])->whereIn('id', $ids)->get();
2. Deferred Join - 自己 JOIN 自己
SELECT users.id, users.name FROM (
SELECT id FROM users ORDER BY id LIMIT 10 OFFSET 10000000
) AS index_users
JOIN users ON users.id = index_users.id
<?php
$subQuery = User::query()->select('id')->orderBy('id')->offset(10000000)->limit(10);
$data = User::query()
->select('users.id', 'users.name')
->rightJoinSub($subQuery, 'index_users', function ($join) {
$join->on('users.id', '=', 'index_users.id');
})
->get();
當然這方式還是有極限,真的還是很慢可能就要考慮分片(Sharding)了。
注碼攻擊 (SQL Injection) 是 PHP 初學者程式碼中常出現的漏洞,就算有使用框架的 ORM 還是很可能會在 Table Column Name 的地方用 Request Parameter 組字串造成 PDO prepare 做白工。
這篇文章著重在攻擊者會如何玩你的系統,在有限的條件下要想辦法取得資料,一步一步把你資料庫中的資料慢慢搬走。
請 ChatGPT 寫一個範例,我直覺他會寫出有漏洞的程式,果不其然有一個地方可以利用,以下是我詠唱的指令
使用 PHP 寫登入會員,登入後有最新消息列表跟內容頁面可以看
裡面有三個預設會員,密碼你設定後跟我說;裡面預設有五篇文章
最後會產生一個 PHP 檔案與一個 SQL 檔案
使用繁體中文回答
Laravel ORM 的三種 paginate 比較:
使用情境 | 效能 | 總頁數 | 可跳頁 | |
paginate | 皆可 | 慢,每次都要 COUNT(*) | 有 | 可 |
simplePaginate | 不需知道資料總數時用 | 中等,資料多時慢 | 無 | 可 |
cursorPaginate | 無限往下滾動的頁面 | 快 | 無 | 不可 |
cursorPaginate 是 Laravel 8.41 推出的新功能:https://laravel-news.com/cursor-pagination。
以往無限滾動的分頁是用 simplePaginate,但 simplePaginate 底層使用的是 MySQL 中的 limit + offset,當頁數越後面時會讀取越慢,因為 offset 會把資料全都抓出來後再丟棄。
SELECT id, name FROM users LIMIT 10 OFFSET 0 -- 很快
SELECT id, name FROM users LIMIT 10 OFFSET 10000000 -- 超慢,會撈取 10000010 筆資料再丟棄前面的 10000000
cursorPaginate 則是使用 orderBy 的欄位來查詢大/小於前一次的值,利用上一次的值往下或往上查詢。
但記得 orderBy 欄位要有索引,否則查詢還是慢,通常這欄位會是 Integer。
SELECT id, name FROM users id > 10000000 ORDER BY id ASC LIMIT 10
因為是紀錄上一次查詢的值,所以上/下一頁網址就不會是 page=123 這種有序的網址,而是將頭尾數值加密放到 cursor 參數裡
http://localhost/users?cursor=eyJpZCI6MTUsIl9wb2ludHNUb05leHRJdGVtcyI6dHJ1ZX0