3. 数据库慢查询定义

慢查询(Slow Query)是指执行时间超过预设阈值的数据库操作。在可观测性体系中,慢查询是衡量数据库健康度的核心指标之一。本章将系统阐述慢查询的判定标准、常见成因,并为后续使用 OpenTelemetry 进行慢查询追踪奠定基础。

3.1 慢查询的判定标准

不同数据库和业务场景对“慢”的定义存在差异,但业界通常采用以下三种判定方式:

判定方式 典型阈值 适用场景 说明
固定阈值 MySQL: long_query_time = 1s
PostgreSQL: log_min_duration_statement = 1s
通用 OLTP 业务 超过阈值即记录,简单直观,但无法区分业务高峰期与低谷期
百分位阈值 P99 延迟 > 500ms 高并发、低延迟要求场景 关注尾部延迟,能反映极端慢查询对用户体验的影响
动态基线 当前延迟 > 历史基线均值 + 3σ 流量波动大的微服务 基于历史数据自动调整,避免误报和漏报
实践建议:在 Go 应用中,建议同时启用固定阈值(如 500ms)和百分位监控。固定阈值用于快速定位异常,百分位监控用于评估整体服务质量。OpenTelemetry 的 Latency 指标配合 Histogram 视图可以同时满足两种需求。

3.2 慢查询的常见原因

慢查询的根因通常集中在以下三个维度。理解这些成因,有助于在 OpenTelemetry 中精准设置观测点和告警规则。

3.2.1 索引缺失

索引缺失是慢查询最常见的原因。当查询条件列没有索引时,数据库必须执行全表扫描(Full Table Scan),导致 I/O 和 CPU 开销剧增。

  • 典型表现:查询计划中出现 Seq Scan(PostgreSQL)或 ALL 类型(MySQL EXPLAIN 输出)。
  • 排查方法:使用 EXPLAIN ANALYZE 查看执行计划,关注 rows_examined 与 rows_sent 的比值。
  • Go 侧观测:通过 OpenTelemetry 的 db.statement 属性记录 SQL 语句,结合数据库慢查询日志,关联分析缺失索引的查询模式。
案例:某订单查询接口 P99 延迟从 200ms 飙升至 3s。通过 OpenTelemetry 追踪发现,慢查询集中在 SELECT * FROM orders WHERE user_id = ?,而 user_id 列未建索引。添加索引后,P99 降至 150ms。

3.2.2 锁等待

锁等待是指一个事务因等待其他事务释放锁而阻塞。锁等待不仅增加查询延迟,还可能引发死锁或连接池耗尽。

  • 常见锁类型:行锁(Row Lock)、间隙锁(Gap Lock)、表锁(Table Lock)。
  • 典型场景:高并发写操作、长事务未提交、DDL 操作阻塞 DML。
  • 观测指标:数据库的 innodb_row_lock_current_waits(MySQL)、pg_locks 视图中的等待状态(PostgreSQL)。
  • Go 侧关联:在 OpenTelemetry 的 Span 中记录 db.lock_wait_duration 属性,或通过数据库插件自动捕获锁等待事件。

3.2.3 数据量过大

即使查询语句和索引设计合理,当单表数据量达到千万级甚至亿级时,B+ 树索引深度增加,磁盘 I/O 次数上升,查询延迟仍会显著增长。

  • 影响范围:排序操作(ORDER BY)、分组操作(GROUP BY)、范围查询(BETWEEN、>、<)。
  • 优化方向:分库分表、数据归档、使用覆盖索引、引入缓存层(如 Redis)。
  • 观测方式:监控 db.rows_returned 和 db.rows_examined 的比值,若比值过低(如 < 1%),说明查询扫描了大量无效数据。

3.3 慢查询判定与根因分析流程

下图展示了从慢查询判定到根因定位的完整流程,帮助你在 Go 应用中建立体系化的慢查询观测能力。

慢查询判定与根因分析流程 SQL 执行 延迟是否超过阈值? 标记为慢查询 根因分析 索引缺失 锁等待 数据量过大 输出优化建议

3.4 慢查询在 OpenTelemetry 中的表示

在 OpenTelemetry Go SDK 中,慢查询通常通过以下方式暴露:

// 使用 OpenTelemetry 数据库插件(如 go-sql-driver/mysql 的 otel 包装)
// 在 Span 中自动记录以下属性:
//   db.system: "mysql"
//   db.statement: "SELECT * FROM orders WHERE user_id = ?"
//   db.operation: "SELECT"
//   db.sql.table: "orders"
//   db.rows_returned: 10
//   db.rows_examined: 50000

// 手动添加慢查询标记
span.SetAttributes(
    attribute.Bool("db.slow_query", true),
    attribute.Float64("db.query_duration_ms", 1200.0),
    attribute.String("db.slow_reason", "index_missing"),
)
注意:不要将慢查询阈值设置得过低(如 10ms),否则会产生大量无意义的 Span,增加存储和网络开销。建议从 500ms 开始,根据业务容忍度逐步调整。同时,务必在数据库侧同步配置慢查询日志,作为 OpenTelemetry 数据的补充验证。

3.5 慢查询与连接池水位的关联

慢查询会直接导致连接池水位上升:每个慢查询长时间占用一个数据库连接,当并发慢查询数量超过连接池最大连接数时,新的请求将排队等待,形成级联阻塞。后续章节将详细讲解如何通过 OpenTelemetry 指标关联分析慢查询与连接池水位的关系。

慢查询类型 对连接池的影响 典型表现
索引缺失 单连接占用时间长,但并发数可控 连接池活跃连接数缓慢上升
锁等待 多个连接互相阻塞,连接池快速耗尽 活跃连接数瞬间打满,大量请求超时
数据量过大 连接占用时间与数据量成正比 连接池水位随查询频率周期性波动