denny / laravel-sql-monitor
A comprehensive SQL query monitoring and analysis tool for Laravel development environments
v1.5.3
2026-04-17 09:00 UTC
Requires
- php: ^8.1
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/http: ^10.0|^11.0|^12.0
- illuminate/routing: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- nikic/php-parser: ^5.0
- phpmyadmin/sql-parser: ^5.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
- phpunit/phpunit: ^10.0|^11.0
README
一個全功能的 Laravel 擴展,提供動態分析(執行時監控)和靜態分析(AST 掃描)能力,幫助開發者在開發時期就發現和優化 SQL 查詢問題。
功能概覽
動態分析(執行後)
| 功能 | 說明 |
|---|---|
| 實時查詢監控 | 捕獲每條 SQL 及執行時間 |
| 複雜度評分 | 自動計算查詢複雜度(0–100) |
| N+1 檢測 | 識別 N+1 查詢模式,代表查詢持久化到 DB |
| 重複查詢檢測 | 找出完全相同的重複查詢,代表查詢持久化到 DB |
| Slow Query 追蹤 | 超過閾值即時寫入 DB,Log::warning 同步記錄 |
| 雙層持久化 | 問題查詢(DB 層)+ 正常查詢(Cache 層,TTL 60s) |
| Dashboard Polling | 每 5 秒自動拉取,合併顯示 DB + Cache 查詢 |
| Call Stack 收集 | IDE 可點擊的調用棧 |
靜態分析(執行前)
透過 AST 解析 PHP 原始碼,不需要執行查詢即可偵測問題:
| 偵測項目 | issue code | 嚴重度 |
|---|---|---|
| SELECT * | select-star |
info |
| 無 WHERE 全表掃描 | no-where |
warning |
| 無 LIMIT 無界結果集 | no-limit |
warning |
| WHERE 欄位缺少索引 | missing-index |
warning |
| 過量 JOIN(>5) | excessive-joins |
critical |
| 偏多 JOIN(>3) | many-joins |
warning |
| JOIN 索引提示 | join-index-hint |
info |
| GROUP BY 索引提示 | group-by-hint |
info |
| LIMIT 無 ORDER BY | limit-without-order |
info |
| UNION 使用提示 | union-detected |
info |
| Eloquent N+1 風險 | n1-risk |
info |
| Raw SQL 偵測 | raw-sql |
info |
| 複合索引建議 | — | — |
安裝
composer require denny/laravel-sql-monitor
發布設定檔:
php artisan vendor:publish --tag=sql-monitor-config
啟用(.env):
APP_ENV=local SQL_MONITOR_ENABLED=true
套件只在
APP_ENV=local或testing時啟動,生產環境不會載入。
靜態分析
基本用法
# 掃描整個 app/ 目錄 php artisan sql-monitor:analyse # 指定目錄 php artisan sql-monitor:analyse --path=app/Repositories # 指定單一檔案 php artisan sql-monitor:analyse --path=app/Models/User.php # 指定類別(完整命名空間或短名稱) php artisan sql-monitor:analyse --class=UserRepository php artisan sql-monitor:analyse --class="App\Repositories\UserRepository"
完整 CLI 選項
| 選項 | 預設 | 說明 |
|---|---|---|
--path= |
— | 分析指定目錄或檔案 |
--class= |
— | 分析指定類別 |
--format= |
table |
輸出格式:table / json / summary |
--min-severity= |
info |
最低顯示嚴重度:info / warning / critical |
--sort= |
file |
排序:file / cost / complexity / severity |
--no-index-check |
— | 跳過資料庫索引檢查(不需 DB 連線) |
--no-index-recommend |
— | 跳過複合索引建議計算 |
輸出範例
=== app/Repositories/OrderRepository.php
▲ OrderRepository::findByUser() 行 24
eloquent::where('user_id', '$userId')->where('status', '$status')->get
表:order | 複雜度:25/100 [OK] | 成本:0.49 [LOW]
ℹ [select-star] 使用了 SELECT *
▲ [no-limit] 無 LIMIT 限制,可能回傳大量資料
▲ [missing-index] WHERE 欄位 `status` 在 `order` 表中無索引
=== 複合索引建議
order(2 個建議,共 15 次查詢覆蓋)
建議 1: (user_id, status) 使用次數:12
→ 等值欄位依選擇率排序:user_id(選擇率 0.920) → status(選擇率 0.005)
⚠ 可 DROP 現有索引:idx_user(已被新索引 left-prefix 覆蓋)
建議 2: (user_id, created_at) 使用次數:3
→ 等值欄位依選擇率排序:user_id(選擇率 0.920)
→ ORDER BY 欄位放尾端:created_at desc(可避免 filesort)
JSON 輸出(適合 CI / diff)
php artisan sql-monitor:analyse --format=json | jq '.summary'
JSON 結構:
{
"generated_at": "2026-04-15T10:00:00+00:00",
"total_sites": 1114,
"summary": {
"critical": 0, "warning": 397, "info": 335, "ok": 382,
"avg_complexity": 11.0, "avg_cost": 1.21
},
"reports": [...],
"index_recommendations": {
"order": [
{
"columns": ["user_id", "status"],
"frequency": 12,
"selectivities": {"user_id": 0.92, "status": 0.005},
"replaces": ["idx_user"],
"patterns": [...]
}
]
}
}
複合索引建議演算法
分析所有 call site 的 WHERE / ORDER BY 模式,依以下規則建構最優複合索引:
- Equality first — 等值條件(
=,IN,IS NULL)放在索引左側 - High selectivity first — 等值欄位之間按選擇率(
COUNT(DISTINCT) / COUNT(*))由高到低排序 - Range last — 範圍條件(
>,<,BETWEEN)放在等值欄位之後(MySQL 限制:每個索引只能使用一個 range 欄位) - ORDER BY tail — 若查詢排序欄位未被覆蓋且方向一致,放在索引尾端以避免 filesort
- Existing index deduplication — 若現有索引已能透過 leftmost prefix 覆蓋此 pattern,不重複建議
- Supersede detection — 若新索引的 left-prefix 完全涵蓋某個現有索引,標記可 DROP
不納入建議的條件:LIKE '%xxx%'(前導萬用字元)、!=、NOT IN、whereDate/Year/Month(非 sargable)。
設定檔
config/sql-monitor.php 完整說明:
return [ // 啟用開關 'enabled' => env('SQL_MONITOR_ENABLED', true), 'environments' => ['local', 'testing'], // 監控連線白/黑名單 'connections' => [], // 空 = 監控所有連線 'excluded_connections' => [], // 永遠不監控(storage / IndexInspector 專用連線) // 持久化儲存(DB 層)— 問題查詢(慢查詢 / complexity >= warning / N+1 / duplicate) 'storage' => [ 'driver' => env('SQL_MONITOR_STORAGE_DRIVER', 'database'), // database(預設):寫入 Laravel 應用程式的 DB,connection = null 時自動跟隨 database.default // sqlite:使用獨立 SQLite 檔案(完全不影響應用程式 DB) 'database' => env('SQL_MONITOR_STORAGE_DATABASE') ?: null, // SQLite 路徑(database driver 不需要) 'connection' => env('SQL_MONITOR_STORAGE_CONNECTION') ?: null, // null = database.default 'table' => 'sql_monitor_logs', 'retention_hours' => 24, ], // Cache 層 — 正常查詢(info / low),TTL 到期自動消失 'memory' => [ 'enabled' => true, 'ttl' => (int) env('SQL_MONITOR_MEMORY_TTL', 60), // 每筆查詢存活秒數 'max_buffer' => 500, ], // Dashboard 自動拉取間隔 'dashboard' => [ 'polling_interval' => (int) env('SQL_MONITOR_POLLING_INTERVAL', 5), // 秒 ], // 靜態分析 log 輸出(tee 模式) 'static_analysis' => [ 'output_path' => env('SQL_MONITOR_ANALYSE_OUTPUT_PATH', storage_path('logs/sql-monitor')), // null / 空字串 = 不寫檔 'analyse_log' => env('SQL_MONITOR_ANALYSE_LOG', 'analyse-{date}.log'), 'suggestion_log' => env('SQL_MONITOR_SUGGESTION_LOG', 'suggestion-{date}.log'), // {date} 佔位符會替換為當天日期(YYYY-MM-DD),每日自動旋轉 // 同一天多次執行:append 模式,不覆蓋 'log_format' => env('SQL_MONITOR_ANALYSE_LOG_FORMAT', 'text'), // text = plain text(人讀)| json = JSON 格式 ], // SQL 複雜度分析 'complexity' => [ 'enabled' => true, 'persist_severity' => env('SQL_MONITOR_PERSIST_SEVERITY', 'warning'), // 複雜度達到此等級(含)以上的查詢持久化到 DB 層 // 可用值:low | info | warning(預設)| critical 'join_threshold' => 5, 'subquery_depth_limit' => 3, 'detect_select_star' => true, 'detect_missing_where' => true, 'detect_like_wildcard' => true, ], // N+1 偵測 'n1_detection' => [ 'enabled' => true, 'threshold' => 2, ], // 重複查詢偵測 'duplicate_detection' => [ 'enabled' => true, ], // Slow Query 追蹤 'slow_query' => [ 'enabled' => true, 'threshold_ms' => (int) env('SQL_MONITOR_SLOW_QUERY_THRESHOLD_MS', 100), ], // WebSocket 即時推送 'live_monitor' => [ 'enabled' => true, 'broadcast_channel' => 'sql-monitor', 'max_buffer_size' => 1000, ], // Call Stack 收集 'stack_trace' => [ 'enabled' => true, 'limit' => 20, 'exclude_vendors' => true, ], 'ide' => env('SQL_MONITOR_IDE', 'vscode'), 'route_prefix' => 'sql-monitor', 'middleware' => ['web'], ];
常用 .env 設定
# 基本啟用 SQL_MONITOR_ENABLED=true # storage driver(預設 database,使用應用程式的 DB) SQL_MONITOR_STORAGE_DRIVER=database # SQL_MONITOR_STORAGE_CONNECTION= # null = database.default(留空即可) # 若需要獨立 SQLite 檔案(不影響應用程式 DB) # SQL_MONITOR_STORAGE_DRIVER=sqlite # Cache 層 TTL(正常查詢在 Dashboard 的可見時間,秒) SQL_MONITOR_MEMORY_TTL=60 # Dashboard polling 間隔(秒) SQL_MONITOR_POLLING_INTERVAL=5 # 複雜度持久化閾值(warning = warning + critical 都寫入 DB) SQL_MONITOR_PERSIST_SEVERITY=warning # 靜態分析 log 輸出 SQL_MONITOR_ANALYSE_OUTPUT_PATH=storage/logs/sql-monitor SQL_MONITOR_ANALYSE_LOG=analyse-{date}.log SQL_MONITOR_SUGGESTION_LOG=suggestion-{date}.log SQL_MONITOR_ANALYSE_LOG_FORMAT=text # 慢查詢閾值(ms) SQL_MONITOR_SLOW_QUERY_THRESHOLD_MS=100 # IDE 跳轉 SQL_MONITOR_IDE=vscode
動態分析
套件在 boot() 時自動完成以下兩件事,無需改動應用程式代碼:
- 監聽
QueryExecuted事件(QueryListener) - 將
QueryMonitoringMiddleware自動掛載到所有 HTTP request(全域 Middleware)
持久化邏輯
| 條件 | 寫入目的地 | 時機 |
|---|---|---|
執行時間 >= slow_query.threshold_ms |
DB 層 | 即時(QueryListener) |
複雜度 >= complexity.persist_severity |
DB 層 | 即時(QueryListener) |
| N+1 pattern 代表查詢 | DB 層 | request 結束(Middleware) |
| 重複查詢代表查詢 | DB 層 | request 結束(Middleware) |
| 其餘查詢(info / low) | Cache 層(TTL 60s) | request 結束(Middleware) |
sql_monitor_logs資料表由套件自動建立(ensureTableExists),無需手動執行 migration。
Web Dashboard
訪問 http://localhost:8000/sql-monitor 查看:
- 查詢摘要與統計(DB 層歷史 + Cache 層近期,每 5 秒自動更新)
- N+1 警告面板
- 重複查詢面板
- 完整查詢日誌(可按 Slow / N+1 / Duplicate / Recent 篩選)
- 歷史慢查詢列表
REST API
GET /sql-monitor/api/queries 查詢列表(DB 層,支援 is_slow / is_n1 / is_duplicate 等 filter)
GET /sql-monitor/api/analytics 分析數據
GET /sql-monitor/api/slow-queries 慢查詢
GET /sql-monitor/api/stats 統計信息
GET /sql-monitor/api/poll Dashboard polling(DB 層 + Cache 層合併)
DELETE /sql-monitor/api/logs 清理日誌
在程式碼中讀取指標
use LaravelSqlMonitor\Lifecycle\RequestQueryManager; use LaravelSqlMonitor\Monitor\MetricsCollector; $manager = app(RequestQueryManager::class); $count = $manager->count(); $stats = $manager->getStats(); // slow_query_count, n1_count, ... $metrics = app(MetricsCollector::class)->collect();
支援的資料庫
動態分析
- MySQL / MariaDB
- PostgreSQL
- SQLite
- SQL Server(基本)
靜態分析 — 索引檢查與選擇率計算
- MySQL / MariaDB(完整支援:INFORMATION_SCHEMA + COUNT DISTINCT)
- SQLite(基本:PRAGMA index_list)
- PostgreSQL(待擴展)
- SQL Server(待擴展)
CI/CD 整合
# 只顯示 warning 以上,有 critical 時 exit code = 1 php artisan sql-monitor:analyse --min-severity=warning --no-index-recommend # JSON 輸出供後續處理 php artisan sql-monitor:analyse --format=json > analyse.json
模組結構
src/
├── StaticAnalysis/
│ ├── Ast/
│ │ ├── AstAnalyser.php # PHP-Parser 驅動的 AST 掃描
│ │ ├── QueryChainExtractor.php # 從 AST 節點提取查詢方法鏈
│ │ └── QueryCallSite.php # 查詢呼叫點資料模型
│ ├── CallSiteAnalyser.php # 規則引擎(偵測 no-limit / missing-index 等)
│ ├── CallSiteReport.php # 分析結果資料模型
│ ├── CompositeIndexRecommender.php # 複合索引建議演算法
│ └── IndexInspector.php # DB 索引 / cardinality 查詢
│
├── Console/Commands/
│ └── AnalyseQueries.php # sql-monitor:analyse 命令
│
├── Core/ # 動態分析核心
│ ├── QueryAnalyzer.php
│ ├── ComplexityDetector.php
│ └── OptimizationSuggester.php
│
├── Lifecycle/
│ ├── N1QueryDetector.php
│ └── DuplicateQueryDetector.php
│
├── Monitor/
│ ├── SlowQueryTracker.php
│ └── LiveQueryMonitor.php
│
├── Storage/
│ ├── SqliteQueryStore.php
│ └── DatabaseQueryStore.php
│
└── Http/
├── Controllers/
└── Routes/
License
MIT License © 2026.