3. 数据库慢查询定义
慢查询(Slow Query)是指执行时间超过预设阈值的数据库操作。在可观测性体系中,慢查询是衡量数据库健康度的核心指标之一。本章将系统阐述慢查询的判定标准、常见成因,并为后续使用 OpenTelemetry 进行慢查询追踪奠定基础。
3.1 慢查询的判定标准
不同数据库和业务场景对“慢”的定义存在差异,但业界通常采用以下三种判定方式:
| 判定方式 | 典型阈值 | 适用场景 | 说明 |
|---|---|---|---|
| 固定阈值 | MySQL: long_query_time = 1sPostgreSQL: 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 应用中建立体系化的慢查询观测能力。
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 指标关联分析慢查询与连接池水位的关系。
| 慢查询类型 | 对连接池的影响 | 典型表现 |
|---|---|---|
| 索引缺失 | 单连接占用时间长,但并发数可控 | 连接池活跃连接数缓慢上升 |
| 锁等待 | 多个连接互相阻塞,连接池快速耗尽 | 活跃连接数瞬间打满,大量请求超时 |
| 数据量过大 | 连接占用时间与数据量成正比 | 连接池水位随查询频率周期性波动 |