游戏卡顿白皮书 › L12 数据库
缺少索引的查询 Missing index / full table scan
原因 ID db-no-index · 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
在含图示和实验的完整版中打开此卡片 →
没有索引时,要找到符合条件的行,就得把整张表读一遍(全表扫描)。
起因 发布新功能时加了一个没有索引的条件查询 → 结果 扫描全部几百万行,一条查询要几百 ms 到几秒 → 画面表现 邮箱、交易记录加载慢,连接被占住,其他请求也跟着等
- 症状
- 操作延迟, 连不上/无限加载
- 因素
- 延迟, 停顿
- 谁会遇到
- 仅特定功能, 全服
- 何时出现
- 做特定操作时
- 负责方
- 主责 研发团队·服务器开发 · 配合 运维团队·数据库运维
- 研发团队要做的事
- 新查询在发布前审查执行计划,补充索引,修改类语句(UPDATE、DELETE)也要确认走了索引。
- 运维团队要做的事
- 监控慢查询日志,找出全表扫描的查询并同步给研发团队,线上加索引采用锁持有时间短的在线方式。
- 数值参考
- 有索引时几 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)、“线上表结构变更(DDL)锁”(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 数据库
其他层中同样导致“操作延迟”的原因
查看含图示和实验的原卡片