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 动态调整的完整链路:
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) 再恢复原值,可强制断开所有空闲连接,使新配置立即生效。