4. 慢查询日志开启与配置:MySQL 慢查询日志参数与 Go 动态配置

慢查询日志是 MySQL 性能调优的“第一现场”。没有日志,调优就是盲人摸象。本章聚焦三个核心参数:slow_query_log(开关)、long_query_time(阈值)、log_queries_not_using_indexes(未走索引查询),并讲解如何在 Go 应用中动态控制这些参数,避免频繁重启数据库。

4.1 核心参数详解

MySQL 慢查询日志由一组系统变量控制。下表列出最关键的三个参数及其默认值与作用域:

参数名 默认值 作用域 说明
slow_query_log OFF 全局/会话 慢查询日志总开关。设为 ON 后开始记录。
long_query_time 10.000000(秒) 全局/会话 执行时间超过此值的 SQL 被记录。单位秒,支持微秒精度(如 0.1)。
log_queries_not_using_indexes OFF 全局 记录所有未使用索引的查询,即使执行时间很短。
slow_query_log_file 主机名-slow.log 全局 日志文件路径。需确保 MySQL 进程有写入权限。
调优建议:生产环境建议将 long_query_time 设为 0.5~2 秒,同时开启 log_queries_not_using_indexes,以便快速发现全表扫描的隐患。

4.2 慢查询日志开启流程

开启慢查询日志有两种方式:临时生效(重启后失效)和永久生效(写入配置文件)。

4.2.1 临时开启(推荐用于排查)

-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;

-- 设置阈值:超过 1 秒即记录
SET GLOBAL long_query_time = 1;

-- 记录未使用索引的查询
SET GLOBAL log_queries_not_using_indexes = ON;

-- 确认参数生效
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_queries_not_using_indexes';

4.2.2 永久开启(写入 my.cnf)

[mysqld]
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 1
log_queries_not_using_indexes = ON
注意:修改配置文件后需重启 MySQL 服务。生产环境重启前请确认业务低峰期,或使用 SET GLOBAL 先动态调整。

4.3 慢查询日志内容解读

一条典型的慢查询日志记录包含以下字段:

# Time: 2025-03-21T10:15:30.123456Z
# User@Host: go_app[go_app] @  [10.0.0.5]  Id: 12345
# Query_time: 2.345678  Lock_time: 0.000123  Rows_sent: 100  Rows_examined: 50000
SET timestamp=1742544930;
SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at DESC;
  • Query_time:SQL 实际执行时间(秒),核心指标。
  • Lock_time:锁等待时间,过高可能表示锁竞争。
  • Rows_examined:扫描行数,远大于 Rows_sent 时说明索引缺失。
  • Rows_sent:返回给客户端的行数。

4.4 在 Go 中动态配置慢查询日志

Go 应用可以通过数据库连接池直接执行 SET GLOBAL 语句,实现动态调整,无需重启 MySQL。这种方式特别适合在压测或故障排查时临时调整阈值。

4.4.1 封装动态配置函数

package mysqlconfig

import (
    "database/sql"
    "fmt"
    "time"
)

// SetSlowQueryThreshold 动态设置慢查询阈值(秒)
func SetSlowQueryThreshold(db *sql.DB, seconds float64) error {
    query := fmt.Sprintf("SET GLOBAL long_query_time = %f", seconds)
    _, err := db.Exec(query)
    return err
}

// EnableSlowQueryLog 开启慢查询日志
func EnableSlowQueryLog(db *sql.DB) error {
    _, err := db.Exec("SET GLOBAL slow_query_log = ON")
    return err
}

// EnableLogNoIndex 开启记录未使用索引的查询
func EnableLogNoIndex(db *sql.DB) error {
    _, err := db.Exec("SET GLOBAL log_queries_not_using_indexes = ON")
    return err
}

// GetSlowQueryStatus 获取当前慢查询配置状态
func GetSlowQueryStatus(db *sql.DB) (map[string]string, error) {
    vars := []string{"slow_query_log", "long_query_time", "log_queries_not_using_indexes"}
    result := make(map[string]string)
    for _, v := range vars {
        var val string
        err := db.QueryRow("SHOW VARIABLES LIKE ?", v).Scan(&v, &val)
        if err != nil {
            return nil, err
        }
        result[v] = val
    }
    return result, nil
}

4.4.2 使用示例:压测前自动调低阈值

func main() {
    db, _ := sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/test")

    // 压测前:开启慢查询,阈值设为 0.5 秒
    mysqlconfig.EnableSlowQueryLog(db)
    mysqlconfig.SetSlowQueryThreshold(db, 0.5)
    mysqlconfig.EnableLogNoIndex(db)

    // 执行压测逻辑...

    // 压测后:恢复默认阈值 10 秒
    mysqlconfig.SetSlowQueryThreshold(db, 10.0)

    // 查看当前配置
    status, _ := mysqlconfig.GetSlowQueryStatus(db)
    for k, v := range status {
        fmt.Printf("%s: %s\n", k, v)
    }
}
最佳实践:在 Go 的压测脚本或诊断工具中,先通过 SET GLOBAL 临时调低 long_query_time,压测结束后恢复。这样既不影响线上长期配置,又能精准捕获压测期间的慢 SQL。

4.5 慢查询日志配置与动态调整流程

下图展示了从配置开启到 Go 动态调整的完整链路:

慢查询日志配置与动态调整流程 my.cnf 永久配置 SET GLOBAL 动态配置 Go 应用动态调用 slow_query_log = ON long_query_time = 1 log_queries_not_using_indexes MySQL 慢查询日志文件(slow_query_log_file) pt-query-digest 分析 Go 定时任务解析告警

4.6 常见配置陷阱与避坑

  • 陷阱1:只开启 slow_query_log 但未设置 slow_query_log_file,日志可能写入默认位置导致磁盘满。
  • 陷阱2:long_query_time 设为 0 会记录所有查询,高并发下瞬间打满磁盘 IO。
  • 陷阱3:Go 中执行 SET GLOBAL 需要 SUPER 权限,生产环境需提前授权。
  • 陷阱4:动态配置只对后续新连接生效,已有连接仍使用旧值。Go 连接池需在调整后重新建立连接。
Go 连接池刷新技巧:执行 SET GLOBAL 后,调用 db.SetMaxOpenConns(0) 再恢复原值,可强制断开所有空闲连接,使新配置立即生效。