遊戲 Lag 白皮書 › L12 資料庫
沒有索引的查詢 Missing index / full table scan
原因 ID db-no-index · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)
在含圖解與實驗的完整版中開啟卡片 →
沒有索引時,要找出符合條件的資料列,就必須讀完整張資料表(全表掃描)。
為什麼 部署新功能時,加入了沒有索引的條件查詢 → 於是 掃描數百萬筆資料列,一個查詢就要數百 ms~數秒 → 畫面上 信箱、交易紀錄載入變慢,連線被占住,連其他請求也要等待
- 症狀
- 輸入延遲, 連不上/無限讀取
- 因素
- 延遲, 停滯
- 誰會遇到
- 只有特定功能, 整個伺服器
- 何時
- 做特定動作時
- 負責單位
- 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(DB 基礎設施)
- 遊戲開發團隊要做的事
- 新查詢在部署前先檢查執行計畫、加上索引,並確認修改資料的查詢(UPDATE、DELETE)也有用到索引。
- 基礎設施團隊要做的事
- 監看慢查詢 log,找出全表掃描的查詢並提供給遊戲開發團隊;營運中新增索引時,採用鎖定時間短的線上(online)方式。
- 數值參考
- 有索引時只要數 ms;沒有索引時會隨資料量等比例變慢,在大型資料表上會慢到數百~數萬倍。
- 圖表上
- 從某個時間點起階梯式上升 · DB 查詢延遲、讀取的資料列數
- 查看位置
- MySQL 看 slow query log(開啟 log_queries_not_using_indexes 後,沒用到索引的查詢也會記錄)的 Rows_examined、Rows_sent,以及 performance_schema events_statements_summary_by_digest 的 SUM_NO_INDEX_USED、SUM_ROWS_EXAMINED,再執行 EXPLAIN。PostgreSQL 看 pg_stat_user_tables 的 seq_scan、seq_tup_read,再執行 EXPLAIN
- 符合的跡象
- 部署後新出現的查詢,讀取的資料列(Rows_examined)比回傳的(Rows_sent)多出數千倍,EXPLAIN 顯示全表掃描(MySQL type ALL、PostgreSQL Seq Scan)。大型資料表的 seq_tup_read 從部署時間點起急遽增加
- 不符合的跡象
- 有用到索引仍然很慢時,是鎖定等待(db-hot-row、db-ddl-lock)或執行計畫改變(db-plan-flip)。小型資料表的全表掃描可能是正常的
- 確認方式
- 用基礎設施工具確認(不需要遊戲程式碼)
- 深入了解
- 受影響的不只是讀取。沒有索引的修改查詢(UPDATE、DELETE)依 DB 不同,可能把掃描過的資料列全都鎖住,連無關玩家的存檔都會被擋住。
出處
- How MySQL Uses Indexes MySQL
沒有索引時會從第一筆資料列開始讀完整張資料表,資料表越大成本越高 - Locks Set by Different SQL Statements in InnoDB MySQL
沒有合適的索引而掃描整張資料表時,所有資料列都會被鎖住,連其他使用者新增資料都會被擋住 - The Slow Query Log MySQL
記錄超過 long_query_time(預設 10 秒)的查詢,也可以另外記錄沒有使用索引的查詢 - CREATE INDEX (PostgreSQL Documentation) PostgreSQL
加上 CONCURRENTLY 建立索引時不會擋住寫入;一般的建立方式會擋住寫入直到完成 - Statement Summary Tables MySQL
events_statements_summary_by_digest:依相同形式的查詢彙總 SUM_NO_INDEX_USED(未使用索引執行的次數)、SUM_ROWS_EXAMINED - EXPLAIN Output Format MySQL
type 為 ALL 表示全表掃描,通常以新增索引來避免 - The Cumulative Statistics System (PostgreSQL Documentation) PostgreSQL
pg_stat_user_tables 的 seq_scan(循序掃描次數)、seq_tup_read(循序掃描讀取的資料列數) - Using EXPLAIN (PostgreSQL Documentation) PostgreSQL
Seq Scan:依序讀取資料表所有資料列的執行計畫
相關原因
同一層:L12 資料庫
同一症狀(輸入延遲)在其他層的原因
查看含圖解與實驗的完整版卡片