81、数据库日志分析:慢查询日志解析、错误日志分析、审计日志、自动化分析工具
日志分析这事儿,说简单也简单,说复杂真能让人头大。我做了十几年嵌入式数据库,最怕半夜被电话吵醒——"数据库挂了,快看看怎么回事!" 这时候,日志就是你唯一的救命稻草。
今天咱们就把数据库日志分析这摊事儿捋清楚。慢查询日志、错误日志、审计日志,还有自动化分析工具,一个一个来。
慢查询日志解析
慢查询日志,说白了就是记录那些"跑得慢"的SQL语句。我习惯把慢查询阈值设在100毫秒,超过这个时间的查询,统统记录下来。
核心思路:不是所有慢查询都要优化,但频繁出现的慢查询必须处理。
先看一个典型的慢查询日志条目:
# Time: 2024-01-15T10:23:45.123456Z
# User@Host: root[root] @ localhost [] Id: 12345
# Query_time: 2.345678 Lock_time: 0.000123
# Rows_sent: 1000 Rows_examined: 500000
SET timestamp=1705314225;
SELECT * FROM sensor_data WHERE device_id = 'DEV-001' AND timestamp > '2024-01-01';
这里有几个关键字段:
- Query_time:查询耗时,2.34秒,明显慢了
- Lock_time:锁等待时间,0.0001秒,基本没锁问题
- Rows_examined:扫描了50万行,但只返回1000行
看到这个比例了吗?50万行扫描只返回1000行,说明索引没用好。我在项目中遇到过类似情况,加个复合索引就解决了。
我的习惯:慢查询日志不要一直开着,生产环境开个5分钟采样就够了。一直开着会影响性能,你想想看,日志写入本身也是I/O操作。
错误日志分析
错误日志这东西,很多人只看最后几行。嗯,这其实是个坏习惯。错误日志里藏着很多"预兆",比如磁盘空间不足、连接数超限、死锁等等。
常见的错误日志分类:
| 错误类型 | 典型信息 | 处理建议 |
|---|---|---|
| 连接错误 | Can't connect to MySQL server | 检查网络、端口、防火墙 |
| 权限错误 | Access denied for user | 检查用户权限配置 |
| 存储错误 | Disk is full | 清理磁盘或扩容 |
| 死锁错误 | Deadlock found | 优化事务顺序,减少锁粒度 |
| 崩溃错误 | Out of memory | 调整内存配置,检查内存泄漏 |
我曾经遇到过一个诡异的问题:数据库每天凌晨3点准时崩溃。查了三天,最后发现是crontab里有个脚本在凌晨3点跑全表扫描,把内存吃光了。错误日志里其实早就记录了OOM信息,只是没人去看。
注意:错误日志会不断增长,一定要配置日志轮转。我见过一个生产环境,错误日志撑爆了根分区,数据库直接挂了。你说冤不冤?
审计日志
审计日志,说白了就是"谁在什么时候干了什么"。在嵌入式数据库里,审计日志尤其重要——设备上的数据往往涉及用户隐私或商业机密。
审计日志通常记录:
- 登录/登出时间
- 执行的SQL语句(特别是DDL和DML)
- 数据导出操作
- 权限变更操作
在SQLite中开启审计日志,可以用钩子函数:
#include <sqlite3.h>
static int audit_callback(
unsigned int event,
sqlite3 *db,
sqlite3_stmt *stmt,
void *user_data
) {
const char *sql = sqlite3_sql(stmt);
if (sql) {
// 记录到审计日志文件
fprintf(audit_log, "[%ld] SQL: %s\n", time(NULL), sql);
}
return 0;
}
// 注册审计回调
sqlite3_audit_hook(db, audit_callback, NULL);
嗯,这里要注意:审计日志本身也会产生I/O,别记录得太细。我建议只记录DDL和敏感表的DML操作,SELECT操作一般不用记。
自动化分析工具
手动翻日志?那是十年前的做法了。现在咱们得用工具。
我常用的几个工具:
- pt-query-digest:Percona Toolkit里的慢查询分析神器,能自动归类相似的慢查询,给出统计报告
- mysqldumpslow:MySQL自带的慢查询汇总工具,轻量级,适合快速查看
- 自研小工具:有时候现成工具不够用,我就写个C程序自己解析
写一个简单的日志解析器,其实不难:
#include <stdio.h>
#include <string.h>
int main() {
char line[1024];
double total_time = 0.0;
int count = 0;
while (fgets(line, sizeof(line), stdin)) {
if (strstr(line, "Query_time:")) {
double qt;
sscanf(line, "%*[^Q]Query_time: %lf", &qt);
total_time += qt;
count++;
if (qt > 1.0) {
printf("慢查询警告: %.3f秒\n", qt);
}
}
}
if (count > 0) {
printf("平均查询时间: %.3f秒 (共%d条)\n",
total_time / count, count);
}
return 0;
}
这个程序虽然简单,但我在嵌入式设备上用了好几年。你想想看,嵌入式设备资源有限,装不了Percona Toolkit,自己写个轻量级解析器反而更实用。
避坑指南:我曾经写过一个日志分析工具,没考虑日志文件编码问题。结果客户那边日志是GBK编码的,解析出来全是乱码。从那以后,我所有日志工具都默认用UTF-8,同时加一个编码检测功能。
日志分析的整体流程
说了这么多,咱们用一张图把整个流程串起来:
这张图把整个流程分成了四层:日志来源、解析方式、分析结果、最终输出。我个人习惯把自动化工具放在第二层,因为手动分析太慢了,尤其是日志量大的时候。
好了,日志分析这块就聊到这儿。记住一句话:日志不是用来存的,是用来分析的。别让日志躺在硬盘里睡大觉。
公众号:蓝海资料掘金营,微信deep3321