Appearance
数据库审计实践
一、数据库审计概述
1.1 什么是数据库审计
数据库审计是指对数据库系统的访问和操作行为进行记录、监控和分析的过程。通过审计,可以追踪谁在什么时间、从哪个 IP、执行了什么 SQL 语句、影响了哪些数据。
1.2 审计的重要性
| 维度 | 说明 |
|---|---|
| 安全合规 | 满足等保 2.0、GDPR、PCI DSS、SOX 等法规要求 |
| 风险追溯 | 发生数据泄露时,可追溯至具体操作人和操作记录 |
| 内部威胁 | 检测和防范内部人员的恶意操作或误操作 |
| 异常检测 | 发现异常的 SQL 访问模式,如批量导出数据 |
| 性能优化 | 分析慢查询,优化数据库性能 |
1.3 等保 2.0 对数据库审计的要求
根据《信息安全技术 网络安全等级保护基本要求》(GB/T 22239-2019):
- 三级系统:应对数据库操作进行审计,审计记录保存 >= 180 天。
- 四级系统:应启用数据库审计功能,对关键操作进行实时告警。
二、MySQL 审计策略配置
2.1 MySQL General Log(通用日志)
General Log 记录所有 SQL 语句,是最基础的审计方式。
bash
# 登录 MySQL
mysql -u root -psql
-- 查看当前 General Log 配置
SHOW VARIABLES LIKE '%general%';
-- 启用 General Log(动态生效,无需重启)
SET GLOBAL general_log = ON;
-- 设置日志文件路径
SET GLOBAL general_log_file = '/var/log/mysql/mysql-general.log';
-- 查看日志文件位置
SHOW VARIABLES LIKE 'general_log_file';bash
# 查看 General Log 内容
tail -f /var/log/mysql/mysql-general.log
# 示例输出
# 2026-07-08T10:30:00.123456Z 5 Query SELECT * FROM employees WHERE id = 1001
# 2026-07-08T10:30:05.654321Z 5 Query INSERT INTO audit_logs (user, action) VALUES ('admin', 'login')General Log 的局限性:
| 优点 | 缺点 |
|---|---|
| 实现简单,不依赖第三方插件 | 记录所有 SQL,日志量大,影响性能 |
| 无额外依赖 | 不支持细粒度过滤(用户、数据库、操作类型) |
| 原生支持 | 生产环境不推荐长期开启 |
2.2 MySQL Audit Plugin(审计插件)
MySQL 企业版提供了审计日志插件(MySQL Enterprise Audit),社区版可使用 MariaDB Audit Plugin 或第三方开源插件。
2.2.1 安装 MariaDB Audit Plugin(社区版推荐方案)
bash
# 下载 MariaDB Audit Plugin
wget https://downloads.mariadb.com/Connectors/audit-plugin/mariadb-audit-plugin.tar.gz
tar -xzf mariadb-audit-plugin.tar.gz
cp lib/mariadb/plugin/server_audit.so /usr/lib/mysql/plugin/sql
-- 安装插件
INSTALL PLUGIN server_audit SONAME 'server_audit.so';
-- 验证安装
SHOW PLUGINS;
-- 应看到 server_audit 状态为 ACTIVE2.2.2 配置审计规则
sql
-- 设置审计日志文件路径
SET GLOBAL server_audit_file_path = '/var/log/mysql/audit.log';
-- 设置审计事件类型(ALL 表示记录所有事件)
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE,QUERY_DDL,QUERY_DML';
-- 排除系统账户的审计(避免过多日志)
SET GLOBAL server_audit_incl_users = '';
SET GLOBAL server_audit_excl_users = 'root,replication';
-- 设置日志轮转(每 10 天自动轮转,保留 30 天)
SET GLOBAL server_audit_file_rotate_size = 104857600; -- 100MB 轮转
SET GLOBAL server_audit_file_rotations = 9;
-- 启用审计
SET GLOBAL server_audit_logging = ON;2.2.3 持久化配置(写入 my.cnf)
ini
[mysqld]
# 审计插件配置
plugin-load = server_audit=server_audit.so
server_audit_logging = ON
server_audit_events = CONNECT,QUERY,TABLE,QUERY_DDL,QUERY_DML
server_audit_file_path = /var/log/mysql/audit.log
server_audit_file_rotate_size = 104857600
server_audit_file_rotations = 9
server_audit_excl_users = root,replication2.3 MySQL Enterprise Audit(企业版)
如果使用 MySQL 企业版,可以直接使用官方审计插件:
sql
-- 安装企业版审计插件
INSTALL PLUGIN audit_log SONAME 'audit_log.so';
-- 审计日志格式(JSON 格式,便于解析)
SET GLOBAL audit_log_format = 'JSON';
-- 审计策略
SET GLOBAL audit_log_policy = 'ALL';
-- 可选值:ALL(所有操作)、LOGINS(登录事件)、QUERIES(查询)、NONE(关闭)JSON 格式的审计日志示例:
json
{
"timestamp": "2026-07-08T10:30:00",
"id": 12345,
"class": "connection",
"event": "connect",
"account": {
"user": "app_user",
"host": "192.168.1.100"
},
"login": {
"user": "app_user",
"os": "linux",
"thread": 1234
},
"connection_type": "ssl",
"status": 0
}三、审计策略配置
3.1 审计粒度策略
根据实际业务需求,选择合适的审计粒度:
| 审计级别 | 记录内容 | 适用场景 |
|---|---|---|
| 全部记录 | 所有 SQL 语句和连接事件 | 合规要求严格的金融行业 |
| 仅 DDL | CREATE/ALTER/DROP 操作 | 数据库结构变更审计 |
| DDL + DML | DDL + INSERT/UPDATE/DELETE | 常规审计需求 |
| 仅登录 | 连接和断开事件 | 基础安全审计 |
| 自定义 | 指定用户/数据库/表 | 精准审计 |
3.2 生产环境推荐配置
sql
-- 生产环境审计配置(平衡性能与安全)
-- 1. 记录所有 DDL 操作(结构变更)
SET GLOBAL server_audit_events = 'QUERY_DDL,CONNECT';
-- 2. 针对敏感表开启 DML 审计
-- 通过应用程序层或触发器实现
-- 示例:对 employees 表创建审计触发器
-- 3. 排除高频监控连接
SET GLOBAL server_audit_excl_users = 'root,monitor,replication,backup';
-- 4. 设置日志轮转
SET GLOBAL server_audit_file_rotate_size = 1073741824; -- 1GB
SET GLOBAL server_audit_file_rotations = 9;3.3 敏感表审计触发器
对于需要精确审计的敏感表,可以使用触发器记录变更:
sql
-- 创建审计日志表
CREATE TABLE audit.employee_changes (
id INT AUTO_INCREMENT PRIMARY KEY,
table_name VARCHAR(64),
operation VARCHAR(16),
old_data JSON,
new_data JSON,
changed_by VARCHAR(128),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
client_ip VARCHAR(45)
);
-- 创建审计触发器
DELIMITER //
CREATE TRIGGER trg_employees_update
AFTER UPDATE ON hr.employees
FOR EACH ROW
BEGIN
INSERT INTO audit.employee_changes (
table_name, operation, old_data, new_data,
changed_by, client_ip
) VALUES (
'employees',
'UPDATE',
JSON_OBJECT(
'id', OLD.id,
'name', OLD.name,
'salary', OLD.salary
),
JSON_OBJECT(
'id', NEW.id,
'name', NEW.name,
'salary', NEW.salary
),
USER(),
SUBSTRING(USER(), INSTR(USER(), '@') + 1)
);
END//
DELIMITER ;四、日志分析
4.1 审计日志格式解析
MySQL Audit Plugin 的日志格式示例:
20260708 10:30:00,web-01,app_user,192.168.1.100,5,12345,QUERY,,'SELECT * FROM employees WHERE id = 1001',0| 字段 | 位置 | 说明 |
|---|---|---|
| 时间戳 | 1 | 事件发生时间 |
| 服务器名 | 2 | 数据库服务器主机名 |
| 用户名 | 3 | 连接用户 |
| 客户端 IP | 4 | 来源 IP 地址 |
| 连接 ID | 5 | 数据库连接 ID |
| 查询 ID | 6 | 查询内部 ID |
| 操作类型 | 7 | CONNECT/QUERY/DISCONNECT |
| 数据库 | 8 | 目标数据库 |
| SQL 语句 | 9 | 执行的 SQL |
| 状态码 | 10 | 0 成功,非 0 失败 |
4.2 日志分析脚本
bash
#!/bin/bash
# 用途:MySQL 审计日志分析脚本
# 保存路径:/usr/local/bin/analyze-mysql-audit.sh
AUDIT_LOG="/var/log/mysql/audit.log"
REPORT_DIR="/var/reports/mysql-audit"
DATE=$(date +%Y%m%d)
mkdir -p "$REPORT_DIR"
echo "========== MySQL 审计日志分析报告 ($DATE) ==========="
echo ""
# 1. 统计操作类型分布
echo "--- 操作类型统计 ---"
awk -F',' '{count[$7]++} END {for (k in count) print k, count[k]}' "$AUDIT_LOG" | sort -k2 -rn
# 2. 统计活跃用户(按操作次数排序)
echo ""
echo "--- 活跃用户排名 ---"
awk -F',' '{users[$3]++} END {for (u in users) print u, users[u]}' "$AUDIT_LOG" | sort -k2 -rn | head -10
# 3. 统计来源 IP
echo ""
echo "--- 来源 IP 统计 ---"
awk -F',' '{ips[$4]++} END {for (ip in ips) print ip, ips[ip]}' "$AUDIT_LOG" | sort -k2 -rn | head -10
# 4. 检测敏感操作(DROP/TRUNCATE/ALTER)
echo ""
echo "--- 敏感操作检测 ---"
grep -iE '(DROP\s+TABLE|TRUNCATE|ALTER\s+TABLE|DELETE\s+FROM)' "$AUDIT_LOG" || echo "未检测到敏感操作"
# 5. 检测失败连接
echo ""
echo "--- 连接失败记录 ---"
grep "CONNECT" "$AUDIT_LOG" | grep -v ",0$" || echo "未检测到连接失败"
# 生成报告文件
{
echo "MySQL 审计日志分析报告"
echo "生成日期: $DATE"
echo "日志文件: $AUDIT_LOG"
echo ""
echo "--- 操作类型统计 ---"
awk -F',' '{count[$7]++} END {for (k in count) print k, count[k]}' "$AUDIT_LOG"
} > "$REPORT_DIR/report_$DATE.txt"
echo ""
echo "报告已生成: $REPORT_DIR/report_$DATE.txt"4.3 使用 Python 进行高级分析
python
#!/usr/bin/env python3
# 用途:MySQL 审计日志结构化分析
# 保存路径:/usr/local/bin/audit_analyzer.py
import csv
import json
from collections import Counter, defaultdict
from datetime import datetime
def parse_audit_log(log_file):
"""解析 MySQL Audit Plugin 日志"""
records = []
with open(log_file, 'r') as f:
for line in f:
parts = line.strip().split(',')
if len(parts) >= 10:
records.append({
'timestamp': parts[0],
'server': parts[1],
'user': parts[2],
'client_ip': parts[3],
'conn_id': parts[4],
'query_id': parts[5],
'event_type': parts[6],
'database': parts[7],
'query': ','.join(parts[8:-1]),
'status': parts[-1]
})
return records
def generate_report(records):
"""生成审计分析报告"""
report = {
'total_events': len(records),
'event_types': Counter(r['event_type'] for r in records),
'top_users': Counter(r['user'] for r in records).most_common(10),
'top_ips': Counter(r['client_ip'] for r in records).most_common(10),
'sensitive_ops': []
}
# 检测敏感操作
sensitive_keywords = ['DROP', 'TRUNCATE', 'ALTER TABLE', 'DELETE']
for r in records:
for kw in sensitive_keywords:
if kw in r['query'].upper():
report['sensitive_ops'].append({
'time': r['timestamp'],
'user': r['user'],
'ip': r['client_ip'],
'query': r['query']
})
break
return report
if __name__ == '__main__':
import sys
log_file = sys.argv[1] if len(sys.argv) > 1 else '/var/log/mysql/audit.log'
records = parse_audit_log(log_file)
report = generate_report(records)
print(json.dumps(report, indent=2, ensure_ascii=False))4.4 日志轮转与归档
bash
# logrotate 配置
cat <<'EOF' | tee /etc/logrotate.d/mysql-audit
/var/log/mysql/audit.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 640 mysql mysql
sharedscripts
postrotate
# 通知 MySQL 刷新日志文件
mysql -e "FLUSH LOGS;" 2>/dev/null || true
endscript
}
EOF
# 手动测试 logrotate
logrotate -vf /etc/logrotate.d/mysql-audit五、合规要求
5.1 等保 2.0 三级要求
| 控制点 | 要求 | 实现方式 |
|---|---|---|
| 安全审计 | 启用审计功能,记录所有数据库操作 | MySQL Audit Plugin |
| 审计内容 | 记录用户、时间、操作类型、操作结果 | 审计插件 + 触发器 |
| 审计记录保护 | 审计日志不可篡改 | 日志异地存储 + 权限控制 |
| 审计记录保存 | 保存 >= 180 天 | 日志轮转 + 归档策略 |
| 审计分析 | 具备审计日志分析能力 | 分析脚本 + 定期报告 |
5.2 数据安全法要求
- 对重要数据的操作行为进行审计记录。
- 审计记录至少保存 6 个月。
- 定期对审计记录进行安全分析。
- 发现异常操作及时告警。
5.3 金融行业合规(PCI DSS)
- 对所有数据库访问进行审计。
- 审计跟踪必须与所有用户活动关联。
- 审计日志必须定期审查。
- 保护审计日志不被修改。
六、最佳实践
6.1 审计策略选择
yaml
# 审计策略配置建议
environment:
production:
audit_events: "CONNECT,QUERY_DDL,QUERY_DML"
excl_users: "root,monitor,replication,backup,scheduler"
log_rotate_size: 1GB
log_retention_days: 180
log_format: "JSON" # 企业版支持
staging:
audit_events: "ALL"
excl_users: "root"
log_rotate_size: 100MB
log_retention_days: 30
development:
audit_events: "CONNECT,QUERY_DDL"
excl_users: "root"
log_retention_days: 76.2 性能影响评估
| 审计方式 | 性能影响 | 建议使用场景 |
|---|---|---|
| General Log | 高(10-20% 性能下降) | 仅临时调试使用 |
| Audit Plugin(DDL 仅) | 低(< 1%) | 生产环境推荐 |
| Audit Plugin(DDL + DML) | 中(3-5%) | 合规要求较高 |
| 触发器 | 低-中(取决于表) | 仅关键表 |
| 第三方审计平台 | 低(旁路抓包) | 大型企业推荐 |
6.3 安全建议
- 审计日志存储:审计日志不应存储在数据库服务器本地,应发送到集中日志平台(如 ELK)。
- 权限隔离:审计日志的访问权限应独立于数据库 DBA 权限。
- 定期审计:每月至少进行一次审计日志分析,形成报告。
- 告警机制:对敏感操作设定实时告警规则(如 DROP TABLE、批量导出)。
- 加密传输:审计日志传输过程应加密,防止中间人篡改。
参考文档:MySQL 官方文档 - Audit Plugin | MariaDB Audit Plugin 文档 | 等保 2.0 标准

