小型專案需關鍵字搜尋並按相關度排序,引入 Sphinx/Elasticsearch 過重,直接用 MySQL ORDER BY 解決。
SELECT *
FROM articles
WHERE (title LIKE '%keywords%') OR (content LIKE '%helloworld%')
ORDER BY (
(CASE WHEN title LIKE '%keywords%' THEN 2 ELSE 0 END) +
(CASE WHEN content LIKE '%helloworld%' THEN 1 ELSE 0 END)
) DESC, dateline DESC;
$kw = preg_replace('/[\s\r\n\t]+/', ' ', trim($kw)); // 合併空白
$kw = preg_replace('/[[:punct:]]/', '', $kw); // 移除標點(視需求保留)
$q = explode(' ', $kw); // 拆分關鍵字陣列
$fields = ['name', 'description']; // 搜尋欄位
$weights = [4, 2]; // 對應權重:名稱 4 分,描述 2 分
$clauses = [];
$scores = [];
$c = 0;
foreach ($q as $keyword) {
foreach ($fields as $i => $field) {
$clauses[] = "`$field` LIKE '%$keyword%'";
$scores[] = "IF(LOCATE('$keyword', `$field`), {$weights[$i]}, 0)";
}
}
$sql = "
SELECT id, name, description,
(" . implode(' + ', $scores) . ") AS score
FROM product
WHERE (" . implode(' OR ', $clauses) . ")
ORDER BY score DESC
";
SELECT id, name, description,
(IF(LOCATE('IBM', `name`), 4, 0) + IF(LOCATE('IBM', `description`), 2, 0) +
IF(LOCATE('伺服器', `name`), 4, 0) + IF(LOCATE('伺服器', `description`), 2, 0)) AS score
FROM product
WHERE (`name` LIKE '%IBM%' OR `description` LIKE '%IBM%' OR `name` LIKE '%伺服器%' OR `description` LIKE '%伺服器%')
ORDER BY score DESC;
| 優化點 | 做法 |
|---|---|
| 避免全表掃描 | 在搜尋欄位建立全文索引 (FULLTEXT),改用 MATCH() AGAINST() |
| 減少 LOCATE 呼叫 | 單關鍵字單欄位只算一次,用子查詢或 CTE 預計算 |
| 限制候選集 | 先用 WHERE MATCH(...) AGAINST(...) 粗篩,再對前 N 筆精細加權 |
| 快取熱門查詢 | Redis 快取關鍵字 → 結果 ID 列表,TTL 5-10 分鐘 |
SELECT *, MATCH(title, content) AGAINST('+IBM +伺服器' IN BOOLEAN MODE) AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST('+IBM +伺服器' IN BOOLEAN MODE)
ORDER BY relevance DESC;
內建全文檢索已支援布林模式與自然語言模式,相關度計算更科學,優先考慮升級版本使用。