Skip to content

数据库审计实践 ​

一、数据库审计概述 ​

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 -p
sql
-- 查看当前 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 状态为 ACTIVE

2.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,replication

2.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 语句和连接事件合规要求严格的金融行业
仅 DDLCREATE/ALTER/DROP 操作数据库结构变更审计
DDL + DMLDDL + 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连接用户
客户端 IP4来源 IP 地址
连接 ID5数据库连接 ID
查询 ID6查询内部 ID
操作类型7CONNECT/QUERY/DISCONNECT
数据库8目标数据库
SQL 语句9执行的 SQL
状态码100 成功,非 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: 7

6.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 标准