数据库性能调优:慢查询、索引与执行计划

📅 2026/6/16 22:51:48
数据库性能调优:慢查询、索引与执行计划
数据库性能调优慢查询、索引与执行计划一、慢查询只是表象排查数据库性能问题通常从慢查询日志入手。但这往往只能看到冰山一角。一条执行 5 秒的 SQL可能只有 0.5 秒在计算剩下 4.5 秒都在等锁、等 I/O 或等内存。真正的问题可能藏在索引缺失、统计信息过期、锁竞争或缓冲池命中率低这些深层因素里。单条查询优化并不等同于系统吞吐量提升。把一条低频查询从 5 秒优化到 0.5 秒对整体系统影响有限。相反一条 50ms 但每秒调用 1000 次的查询才是吞吐量的瓶颈。调优时建议先观察系统级指标CPU、I/O、锁、内存再定位热点查询最后分析执行计划。先宏观再微观。二、执行计划与索引看执行计划是调优的基础。以 MySQL 或 PostgreSQL 为例重点关注扫描类型、预估行数、实际行数以及是否使用了索引。graph TB subgraph 扫描类型 A[ALL: 全表扫描] -- B[index: 索引扫描] B -- C[range: 范围扫描] C -- D[ref: 等值查找] D -- E[eq_ref: 唯一索引查找] E -- F[const: 常量查找] end subgraph 优化思路 G[WHERE 无索引] -- H[加单列索引] H -- I[多列条件仍全表] I -- J[加复合索引] J -- K[SELECT * 回表] K -- L[覆盖索引] L -- M[ORDER BY 文件排序] M -- N[索引排序] end subgraph 统计信息 O[统计信息过期] -- P[行数估算偏差] P -- Q[优化器选错索引] Q -- R[计划退化] end索引选择性DISTINCT 值数量 / 总行数。选择性太低比如 0.01索引过滤效果差全表扫描反而更快。复合索引要注意列顺序高选择性列放前面。覆盖索引查询所需列都在索引里数据库不用回表。这能把“索引查找 数据页读取”变成“纯索引读取”I/O 通常能减少一半以上。高并发场景下这是性价比最高的优化方式。统计信息优化器靠统计信息行数、列分布、选择性做决策。数据大幅变更后记得手动运行ANALYZE TABLEMySQL或ANALYZEPostgreSQL更新统计否则优化器可能选错索引或 JOIN 顺序。三、慢查询分析脚本下面是一个简单的 Python 脚本用于分析慢查询日志并推荐索引。核心逻辑是计算impact_score执行时间 × 调用频率优先优化对吞吐量影响最大的查询。import re from dataclasses import dataclass from typing import Optional dataclass class SlowQuery: query_text: str execution_time_ms: float rows_examined: int rows_sent: int scan_type: str index_used: Optional[str] None call_frequency_per_hour: int 0 property def impact_score(self) - float: return self.execution_time_ms * self.call_frequency_per_hour class QueryAnalyzer: def __init__(self): self.queries [] def add_query(self, query: SlowQuery): self.queries.append(query) def rank_by_impact(self): return sorted(self.queries, keylambda q: q.impact_score, reverseTrue) def recommend_index(self, query: SlowQuery): where_cols self._extract_cols(query.query_text, rWHERE\s(.?)(?:ORDER|GROUP|LIMIT|$)) if not where_cols: return None # 简化逻辑根据条件列数量推荐 if len(where_cols) 1: return f建议为 {where_cols} 创建复合索引 return f建议为 {where_cols[0]} 创建单列索引 def _extract_cols(self, sql: str, pattern: str): match re.search(pattern, sql, re.IGNORECASE) if not match: return [] # 这里只是简单提取实际场景需要更复杂的 SQL 解析 return re.findall(r(\w)\s*(?:|IN||), match.group(1), re.IGNORECASE)实际生产中pt-query-digest或EXPLAIN ANALYZE往往更实用。这个脚本的思路是不要只看执行时间要结合调用频率看影响。四、索引的代价索引不是免费的。写入性能每次 INSERT/UPDATE/DELETE 都要维护索引。表有 5 个索引写入时就要更新 6 棵 B 树。高写入场景下索引维护可能让写入吞吐量下降 30%-50%。存储空间复合索引可能占用表空间 20%-40%。亿级大表上索引碎片会导致物理存储分散增加 I/O。定期OPTIMIZE TABLE或REINDEX能解决碎片但会锁表。优化器失误多个可用索引时优化器可能选错。常见原因是统计信息过期。虽然可以用FORCE INDEX强制指定但这只是临时方案。数据分布变化后强制索引可能更差。根本解决办法还是更新统计信息或调整索引设计。五、小结调优时先找系统瓶颈再找热点查询最后看执行计划。索引最有效但也有写入和存储代价。覆盖索引适合高并发复合索引要注意列顺序统计信息要及时更新。调优本质上是在查询性能、写入性能和存储成本之间找平衡。