面對單表 4000 萬行、單日 8GB 的 HTTP 協議流水日誌表,多欄位組合查詢耗時 20-30 分鐘。常規索引優化收效有限,最終透過業務流程細節分析,採用分段查詢解決明細查詢、二次預聚合解決統計查詢,將響應時間壓縮至 30 秒內。本文還原完整優化思路。
http_log_YYYYMMDD 分表,單表 ~4000 萬行,.MYD ~8GB。VARCHAR(256)。log_time 索引 (8 bytes × 4000萬 = 320MB),查詢稍快但仍需回表掃描大量行。(src_ip, log_time) 等,維護成本高,查詢計畫常走全表掃描。核心思想:將 4000 萬行按主鍵 ID 範圍均分為 10 段 (每段 400 萬),前端提供下拉選單讓用戶選擇段區,後端加 WHERE id BETWEEN start AND end。
-- 前端傳入 segment=3 (對應 800萬-1200萬)
SELECT * FROM http_log_20240115
WHERE id BETWEEN 8000000 AND 12000000
AND src_ip = '1.2.3.4'
AND url LIKE '%login%'
LIMIT 100;
效果:單段掃描 400 萬行,索引命中後回表極少,耗時 20-30 秒。犧牲「一次查全表」便利性,換取可接受的響應速度。
現狀:一級預聚合 (月表) 仍過大,跨維度聚合慢。
方案:針對高頻統計維度 (如「每小時各狀態碼計數」、「每日 Top 100 URL」),建立二級預聚合表,粒度更細、欄位更少、行數極少 (千行級)。
-- 二級預聚合表示例:每小時狀態碼統計
CREATE TABLE http_log_hourly_status (
log_date DATE NOT NULL,
log_hour TINYINT UNSIGNED NOT NULL,
status_code SMALLINT UNSIGNED NOT NULL,
req_count BIGINT UNSIGNED NOT NULL,
bytes_sum BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (log_date, log_hour, status_code)
) ENGINE=InnoDB;
-- 定時任務 (每小時跑上小時資料)
INSERT INTO http_log_hourly_status
SELECT DATE(log_time), HOUR(log_time), status_code, COUNT(*), SUM(bytes)
FROM http_log_20240115
WHERE log_time >= '2024-01-15 13:00:00' AND log_time < '2024-01-15 14:00:00'
GROUP BY DATE(log_time), HOUR(log_time), status_code
ON DUPLICATE KEY UPDATE req_count=VALUES(req_count), bytes_sum=VALUES(bytes_sum);
效果:用戶查詢「過去 7 天 404 趨勢」直接掃描 http_log_hourly_status (7×24×幾百狀態碼 ≈ 5 萬行),耗時 < 1 秒。
EXPLAIN、HandlerSocket、Perf4j 量化每條 SQL 耗時,避免主觀臆測。ON DUPLICATE KEY UPDATE 或 REPLACE INTO),支援重跑補數。