简介:针对 MySQL 5.7 社区版的 audit-plugin 审计插件安装包,面向 Linux x86_64 环境下的数据库管理员与安全运维人员,用于解决数据库活动监控、安全审计与合规追溯需求。压缩包仅 568KB,共 6 个文件,其中包括 3 个 txt 说明文档、1 个 so 动态库、1 个 sh 辅助脚本及版权声明文件,核心插件为 libaudit_plugin.so 动态库,配套文本说明与脚本便于快速完成插件加载与基础配置。已有 1143 人学习下载。整体虽精简,却构成 MySQL 5.7 审计插件的最小可用集合:部署后可实现 SQL 语句全量记录、按规则过滤日志、捕捉登录失败与权限变更等安全事件,为满足等保合规和日常数据库行为分析提供直接的审计支撑。对于希望快速在社区版环境中启用审计能力、又需要明确文件用途的运维人员来说,是一份清晰可用的离线安装参考。
1. MySQL 5.7 社区版安全审计缺口:一个二进制包补上“谁在何时执行了哪条SQL”
MySQL 5.7 社区版没有自带安全审计插件,这是用过的人都知道的一个痛点。binlog 只能告诉你哪些语句改了数据,却答不出“是谁通过哪个账号、从哪台机器连进来,执行了哪条 SELECT”。等到数据被误删、权限被冒用,翻遍日志也找不到第一现场。这个名为 audit-plugin-mysql-5.7-1.1.7-921-linux-x86_64.zip 的二进制包,就是用来补这个缺口的。它是社区长期维护的 mysql 审计插件,专配 MySQL 5.7 Linux 64 位环境,装上之后可以把连接、查询、表访问都记成结构化日志。适合被合规检查逼着补审计、又不想为商业版付年费的中小团队,也适合准备做数据库安全基线的一线运维。
2. audit-plugin 到底是什么:审计事件模型、版本匹配与记录字段拆解
2.1 审计插件和普通日志的区别:binlog 答不了的问题它来答
MySQL 自带的日志家族里,error log 只记故障,slow log 只记慢查询,binlog 只记会改变数据的语句,而且都不带完整的客户端来源信息。审计插件做的事情是站在服务层统一截获事件——无论语句有没有改数据、有没有命中索引、是成功了还是报错了,它都能按规则落一条记录。这个思路和数据库防火墙类似,但实现位置不同:它不是代理,而是直接以插件形式装在 MySQL 内部,对应用透明。
常见做法是在安装后把连接事件与查询事件全部打开,这样任何账号的任何尝试都会被记下来,包括失败的登录。这一点对排查“谁在爆破密码”“谁半夜连进来跑了一堆查询”特别有用。audit-plugin 在这类场景里补的是两个缺失:一是来源 IP,二是用户到语句的完整映射。因为审计插件记录的是线程级事件,每行日志里能同时看到 user、host、ip、thread_id、database、query,这个组合是其余常规日志给不了的。
2.2 版本号拆解:为什么 1.1.7-921 只面向 5.7
文件名 audit-plugin-mysql-5.7-1.1.7-921-linux-x86_64.zip 里其实写了四个关键信息:面向的数据库大版本是 MySQL 5.7,插件本身版本是 1.1.7,921 是构建号(对应特定的源码提交与编译选项),架构是 linux-x86_64。也就是说,这份包是专门为 5.7 系列编好的二进制,不需要你本地有编译工具链,解压后直接加载即可。插件的源码对外是开放的,但自己从源码编容易在 glibc、openssl、MySQL 源码版本上翻车,社区分发预编译包就是为了省掉这一步。
需要强调一个边界:MySQL 8.0 之后的审计方案和 5.7 不完全通用,8.0 自带的 audit log 插件是另一个体系。这份 1.1.7-921 是 5.7 的配套件,别拿去装 8.0,也别拿 8.0 的 audit 插件反过来装 5.7。我一般会在装之前先确认 server 版本号,SELECT VERSION();看一眼,再决定用哪份包。版本不匹配时,插件加载过程通常不会报错,但部分事件记录会丢失或字段错位,这类问题是典型的黑匣子故障,后面避坑章会展开。
2.3 事件模型:CONNECT / QUERY / TABLE 分别记录了什么
audit-plugin 的事件类型并不复杂,核心是三类:CONNECT 记录连接建立和断开,QUERY 记录每一条到达服务端的语句,TABLE 记录语句实际访问了哪些表。QUERY 还可以细分成 QUERY_DDL、QUERY_DML、QUERY_DCL,目的是让你在后续分析时能快速按语句类别过滤。事件类别通过 server_audit_events 参数控制,多个类别用逗号分隔,也可以直接写 ALL。
每条日志的字段顺序基本固定,典型文本格式按行输出,字段包括时间戳、服务器名、用户名、来源 IP、线程 ID、连接 ID、事件类别、数据库名、返回状态、语句文本。这里有个容易忽略的点:TABLE 事件的记录粒度是“这条语句触碰了哪几张表”,而不是“对表发起了什么操作”,所以一张表被一条语句扫过,会记一行 TABLE 事件;一条语句涉及三张表,就会记三条。分析时如果想还原单条语句的完整行为,要按连接 ID 和线程 ID 做关联,不能只数行数。这个模型在合规审计里够用,但如果你追求 Oracle 那种细粒度的权限审计,得自己再叠一层应用层的日志。
3. 安装与加载:从解压 ZIP 到 SHOW PLUGINS 验证的完整过程
3.1 安装前检查:确认 MySQL 版本、插件目录与文件完整性
安装最忌讳的是上来就解压,解压完找不到放哪。我先做三件事:确认版本、确认平台、确认插件目录。用下面三组命令把环境信息取出来,再和文件名里的 5.7、x86_64 做对照。
# 确认 MySQL 版本,应与文件名中的 5.7 匹配 mysql -uroot -p -e "SELECT VERSION();" # 确认系统架构,x86_64 对 x86_64,不要跨架构用 uname -m # 确认 MySQL 插件目录,.so 文件要放到这里 mysql -uroot -p -e "SHOW VARIABLES LIKE 'plugin_dir';"逻辑说明:SELECT VERSION()拿的是服务端真实版本,5.7.20、5.7.44 都在 1.1.7-921 的兼容范围内;uname -m在绝大多数服务器上返回 x86_64,如果返回 aarch64,这份包就不能用;SHOW VARIABLES LIKE 'plugin_dir;返回的路径是 MySQL 在启动时就确定好的插件搜索目录,拷贝错了路径,INSTALL PLUGIN 阶段会直接报找不到共享库。参数含义上,plugin_dir 的值通常类似/usr/lib/mysql/plugin,在部分发行版里可能是/usr/lib64/mysql/plugin,以实际输出为准。
拿到这些信息之后,再校验一遍 zip 包的完整性。压缩包在传输过程中损坏是常事,坏包解压时不会立即报错,等到加载库文件时才抛畸形文件错误。我一般压完包先unzip -t测一下,出现 OK 再继续。
3.2 解压与部署:把 .so 放进 plugin_dir
将压缩包放到服务器临时目录后执行解压,找到插件动态库文件。不同构建版本解压出的文件名不完全一致,常见的是libaudit_plugin.so,解压后先ls -la确认文件权限和所属用户。
# 解压到 /opt/audit-plugin 目录 mkdir -p /opt/audit-plugin cd /opt/audit-plugin unzip audit-plugin-mysql-5.7-1.1.7-921-linux-x86_64.zip # 查看解压出的文件,确认 .so 文件存在 ls -la # 将插件库拷贝到 MySQL 插件目录 cp libaudit_plugin.so /usr/lib/mysql/plugin/ # 确认拷贝后的权限,建议与 plugin 目录下其他 .so 保持一致 ls -l /usr/lib/mysql/plugin/libaudit_plugin.so逻辑说明:cp的目标路径是 3.1 里查到的 plugin_dir,不要自己想当然写/usr/lib64/mysql/plugin,有些发行版两个目录都存在但 MySQL 只认其中一个。权限上,插件库要被 mysqld 进程读取,属主建议设为 mysql:mysql,权限至少 644;如果之前发生过权限错乱,把权限收紧到 640 也可能导致加载失败。这一步做完后最好顺手执行ldd libaudit_plugin.so,查看动态链接依赖是否完整,缺依赖时ldd的输出里会出现not found字样,这能提前暴露 glibc 或安全库不兼容的问题,避免装完才发现插件起不来。
3.3 加载与验证:INSTALL PLUGIN 到 SHOW PLUGINS 的完整命令
把 .so 放进 plugin_dir 只算部署了一半,MySQL 还没有加载它。加载有两种方式:动态安装适合第一次验证,把配置写进 my.cnf 适合长期使用。先看动态安装:
-- 动态加载审计插件,SONAME 必须与 plugin_dir 中的文件名一致 INSTALL PLUGIN audit SONAME 'libaudit_plugin.so'; -- 查看插件是否出现在插件列表中 SHOW PLUGINS; -- 另一种验证方式:从系统表查询插件状态 SELECT PLUGIN_NAME, PLUGIN_STATUS, PLUGIN_TYPE FROM information_schema.plugins WHERE PLUGIN_NAME = 'audit';逻辑说明:INSTALL PLUGIN 会在 mysql.plugin 系统表中写入一条记录,同时把插件加载进内存。SONAME 参数对应的是 plugin_dir 下的文件名,填错会报 ERROR 1126。SHOW PLUGINS 输出里如果能看到 audit 且 Status 为 ACTIVE,说明加载成功。从 information_schema 查到的 PLUGIN_STATUS 字段更稳定,适合写进自动化脚本做断言。参数方面,INSTALL PLUGIN 本身不需要额外参数,真正的开关参数是安装完成后的 server_audit_logging,这一步只负责把插件代码装载进来。
动态安装的问题在于实例重启后插件不会自动加载,除非 mysql.plugin 表里已经有记录,而 INSTALL PLUGIN 确实会持久化这条记录,所以这里其实不存在重启丢失的问题。更稳妥的做法是直接在 my.cnf 里声明插件并带上初始化参数,一次到位。下面是把插件配置写进 [mysqld] 段的标准写法:
[mysqld] plugin-load-add=audit=libaudit_plugin.so server_audit_logging=ON server_audit_events=CONNECT,QUERY,TABLE server_audit_file_path=/var/log/mysql/audit.log server_audit_file_rotate_size=50M server_audit_file_rotations=10逻辑说明:plugin-load-add 是 MySQL 5.7 用来在启动阶段加载插件的配置项,格式为插件名=库文件名,多项插件用分号分隔。server_audit_logging 是审计总开关,这里直接设 ON,避免实例起来后还要手工开启。server_audit_events 指定记录哪些事件,CONNECT 和 QUERY 是必开项,TABLE 是否开启取决于你是否需要表级访问轨迹。server_audit_file_path 指定审计日志路径,注意 mysqld 进程对该目录要有写权限,否则日志不会生成,后面避坑章会讲这个。日志轮转相关参数提前设置好,能避免日志文件无限膨胀把磁盘撑满。
重启 MySQL 后再执行一次SHOW PLUGINS,看到 audit 状态为 ACTIVE,同时用SHOW VARIABLES LIKE 'server_audit%';能查到刚才在配置文件里设置的参数,就说明插件已经随实例自动加载,部署环节闭环了。
4. 配置与调优:用一组 server_audit_* 参数把日志写成可检索数据
4.1 打开审计开关:最少需要的三条配置
插件加载只是第一步,真正开始记录要满足三个条件:总开关打开、事件类别非空、日志输出路径可写。最少配置如下:
-- 打开审计总开关 SET GLOBAL server_audit_logging = ON; -- 指定记录的事件类别 SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE'; -- 确认日志输出路径 SHOW VARIABLES LIKE 'server_audit_file_path';逻辑说明:第一条语句是开关,把 audit 从待命状态切到工作状态。第二条语句设置事件类别,这里用引号包裹,逗号分隔;如果漏掉这条,插件默认只记录 CONNECT 事件,你会发现日志里只有登录和登出,查询记录一条都没有,这是最高频的误配。第三条语句不是配置,是确认,确保路径下能生成文件。server_audit_events设置成ALL也可以,但会把一些内部系统事件也带进来,日志噪声明显变大,我一般按需开 CONNECT、QUERY、TABLE 三类就够用。注意这些变量支持动态设置,不用重启,但重启后要保留配置,仍然要把参数写进 my.cnf。
4.2 参数表:控制事件、用户、轮转的常用参数
审计插件的行为全部通过server_audit_*系列变量控制,下面是日常配置的参考表。各版本默认值略有差异,实际以SHOW VARIABLES LIKE 'server_audit%';输出为准。
| 变量名 | 参考值 | 作用 |
|---|---|---|
| server_audit_logging | ON / OFF | 审计总开关 |
| server_audit_events | CONNECT,QUERY,TABLE | 记录哪些事件类型 |
| server_audit_output_type | file | 输出到文件或 syslog |
| server_audit_file_path | /var/log/mysql/audit.log | 日志文件路径 |
| server_audit_file_rotate_size | 50M | 日志达到该大小触发轮转 |
| server_audit_file_rotations | 10 | 保留的轮转文件个数 |
| server_audit_incl_users | 空 | 只记录指定用户 |
| server_audit_excl_users | 空 | 排除指定用户,优先于 incl |
| server_audit_query_log_limit | 1024 | 单条 SQL 记录的最大字节数 |
参数说明:前两条决定“记录什么”,中间三条决定“写到哪里、怎么分卷”,最后三条决定“记录谁、记多长”。server_audit_incl_users和server_audit_excl_users都为空时表示对所有用户审计;两者同时设置时,excl 优先生效。server_audit_query_log_limit如果设得太小,长 SQL 会被截断,影响后续分析;设得太大,日志文件增长快。我一般设为 4096,覆盖绝大多数业务语句。server_audit_file_rotations为 0 时表示轮转后不清理旧文件,配合巨大的 rotate_size 会把磁盘写满,这个坑在避坑章里单独讲。
4.3 日志格式与字段解读:拿到一行日志读出完整故事
默认文本格式的审计日志每行一条记录,字段按固定顺序排列,典型的一行长这样:
20250121 14:23:45,db-server-01,app_user,10.0.20.5,11832,7,0,QUERY,pay_db,0,0,SELECT * FROM accounts WHERE id=1001字段含义按顺序拆开看:第一段是日期时间,第二段是服务器名,第三段是用户名,第四段是客户端 IP,第五段是线程 ID,第六段是连接 ID,第七段是语句执行状态码,第八段是事件类型,第九段是当前数据库,第十段是错误码,第十一段是影响行数或结果集大小,第十二段是完整语句。排查问题时,先按 IP 过滤出可疑客户端,再按连接 ID 把同一会话的所有语句串起来,就能还原整个操作链路。这里有个细节:QUERY 事件的最后一段里包含的是语句原文,但 MySQL 也会把注释一起记进来,分析时建议先用正则把注释剥掉再归类。
输出到 syslog 时字段相同,只是外面包了 syslog 头。想拿 JSON 格式的话,某些构建版本提供 JSON 输出能力,具体看解压包里的 README 或 INSTALL 文档,我这边使用文本格式居多,因为文本格式可以直接灌入日志采集器,兼容性最好。
4.4 落库为表:让审计日志告别“查不动”
日志文件一旦上了规模,用 grep 逐行翻就吃力了。常见做法是把日志导入 MySQL 或 ClickHouse 建表,之后就可以用 SQL 做聚合查询。建表结构不需要 1:1 还原全部字段,把常用分析字段拆出来就够了。
CREATE TABLE audit_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, ts DATETIME, server_name VARCHAR(64), user_name VARCHAR(64), client_ip VARCHAR(64), thread_id INT, conn_id INT, status_code INT, event_type VARCHAR(16), db_name VARCHAR(64), query_text TEXT, INDEX idx_ts(ts), INDEX idx_user(user_name), INDEX idx_ip(client_ip) );逻辑说明:这张表把审计日志里的核心字段拆成独立列,ts 建索引用于时间范围查询,user_name 和 client_ip 建索引用于追查指定账号或指定来源。query_text 用 TEXT 存放完整语句,不需要建索引,因为查询语句做的是 LIKE 匹配。导入时按行解析日志文本,按字段位置灌入库;也可以用脚本批量读入,脚本逻辑在最后一章给出。参数上,如果审计量很大,建议按天做分区表,比如按 ts 的日期分区,避免单表数据膨胀后查询变慢。对中小团队来说,这张表存上三个月没有问题,再久就把历史分卷归档。
5. 避坑指南:5.7 社区版加载审计插件的五个高频问题
5.1 加载时报 ERROR 1126:Can't open shared library
现象:执行INSTALL PLUGIN audit SONAME 'libaudit_plugin.so';直接报错,错误码 1126,提示无法打开共享库。
原因:常见原因有三个,一是 .so 文件没有拷到正确的 plugin_dir,二是文件权限不足导致 mysqld 进程读不了,三是动态链接依赖缺失。前两个最容易查,最后一个要通过ldd libaudit_plugin.so确认。
解决:先执行SHOW VARIABLES LIKE 'plugin_dir';拿到真实路径,把文件拷过去再试;再看文件权限,确保 mysqld 启动用户可读;最后执行ldd检查是否缺库。多数场景是路径没放对,不是包坏了。血泪经验是,不要在多个 plugin 目录之间凭感觉猜,MySQL 只认变量输出那一个。
5.2 日志文件没生成或目录报权限错误
现象:插件加载成功、总开关也打开了,但配置的日志路径下始终没有文件,或者 MySQL 错误日志里出现 permission denied。
原因:server_audit_file_path 配置的目录是 root 创建的,目录权限是 700,mysqld 进程的用户写不进去。另一个隐蔽情况是路径是软链接,链接目标指向了无权限的目录。
解决:把日志目录属主改为 mysql:mysql,再给 755 权限;或者直接把日志目录指向 /var/log/mysql 这类已授权的位置。改完配置后不用急着重启,SET GLOBAL server_audit_logging = OFF;再 ON 一次,看文件是否创建。从那以后我每次配路径都会先手动touch一个测试文件,确认能写再开审计。
5.3 日志里只有连接记录,没有查询记录
现象:审计日志一直在增长,但几乎全是 CONNECT 事件,QUERY 记录一条都没有。
原因:server_audit_events 没有显式设置 QUERY,插件按默认值只记录了 CONNECT。这种问题最坑,因为系统没报错,看起来一切正常,实际上关键动作全漏了。
解决:执行SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';,再观察日志是否出现 QUERY 行。同时检查有没有账号被列进了 server_audit_excl_users,如果业务账号恰好被排除,它的查询也不会记录。这个检查要写进部署 checklist,每次装完都查一遍再交接。
5.4 日志无限增长,轮转不生效
现象:磁盘空间被 audit.log 占满,数据库实例直接进入只读或不可用状态。
原因:server_audit_file_rotate_size 设的过大或为 0,又或者是日志文件还没到触发条件,而 server_audit_file_rotations 为 0 表示旧文件永不清理。两个参数叠加,磁盘撑满是迟早的事。
解决:把 rotate_size 调到业务可接受的阈值,比如 50M 或 100M;把 rotations 设为不小于 7 的值,保证至少保留一周日志。如果想立即触发一次轮转,执行SET GLOBAL server_audit_file_rotate_now = ON;能看到目录下生成 audit.log.001 之类的分卷。这个坑值得记重点,审计插件本身不会帮你磁盘瘦身,轮转参数不设对就是定时炸弹。
5.5 主从环境里从库重复记录
现象:主库开启审计后,从库也装了插件,结果同一批 SQL 在主库和从库日志里各出现一次,对账时数据翻倍。
原因:从库的 SQL 线程在应用主库传来的 binlog 时,也会触发服务层事件,审计插件分辨不了是人工执行还是重放执行,一并记了下来。
解决:一般只在主库开启审计;如果从库必须开,就把从库的 audit 日志输出到独立的 syslog facility,或者用 server_audit_excl_users 暂时排除复制账号,但这并不能完全覆盖内部重放线程。另一种思路是分析时按 server_name 字段过滤,只取主库记录。生产环境建议统一策略,别让主从两套审计日志混在一起,后续做分析会非常痛苦。
6. 进阶:审计日志自动化分析的一个小脚本实践
日志落盘之后,真正涨功夫的是怎么让日志主动告诉你“今天谁在折腾”。手工 grep 只适合排查单点问题,持续运行的分析还得交给脚本。我常用的是 Python 按行解析审计日志,把语句做归一化,统计高频 SQL,再按用户和来源聚合,直接输出一份排名。
下面这个脚本解析的是默认逗号分隔格式,思路是按字段位置拆行,对 QUERY 事件做语句归一化,输出 TOP 10 高频语句。
import re from collections import Counter LOG_PATH = "/var/log/mysql/audit.log" def parse_audit_line(line): parts = line.rstrip("\n").split(",", 11) if len(parts) < 12: return None return { "ts": parts[0] + " " + parts[1], "server": parts[2], "user": parts[3], "client_ip": parts[4], "event": parts[8], "db": parts[9], "query": parts[11], } def normalize_query(sql): # 去掉注释与字符串字面量,数字统一替换为 ?,便于跨参数聚合 sql = re.sub(r"/\*.*?\*/", "", sql) sql = re.sub(r"\'\''.*?\'\''", "?", sql) sql = re.sub(r"\b\d+\b", "?", sql) return re.sub(r"\s+", " ", sql).strip() counter = Counter() user_counter = Counter() with open(LOG_PATH, "r", encoding="utf-8", errors="ignore") as f: for line in f: rec = parse_audit_line(line) if not rec or rec["event"] != "QUERY": continue norm = normalize_query(rec["query"]) counter[norm] += 1 user_counter[rec["user"]] += 1 print("高频 SQL TOP 10:") for sql, cnt in counter.most_common(10): print(f"{cnt:6d}\t{sql}") print("\n高频用户 TOP 5:") for user, cnt in user_counter.most_common(5): print(f"{cnt:6d}\t{user}")脚本逻辑不复杂:parse_audit_line 负责按逗号切分字段,第 12 个字段(下标 11)是语句原文;normalize_query 把注释、字符串、数字统一替换成占位符,这样SELECT * FROM t WHERE id=1和SELECT * FROM t WHERE id=999会归为同一条 SQL,聚合才有意义。脚本输出两部分:高频 SQL 排名和活跃用户排名,前者帮助发现潜在的全表扫描或者热点查询,后者帮助识别异常账号。
如果想从日志里找“谁删了这张表”,只要在脚本里加一个过滤条件:if "DROP TABLE" in rec["query"].upper(): print(rec),就能把命中事件全部打印出来。更完整的自动化可以配合 cron 每日执行,输出结果追加到统计表,再接入告警。运行环境上,只要服务器有 Python 3 就能跑,不依赖第三方库,对生产环境的侵入很小。从那以后我每次给 MySQL 5.7 做审计基线,都强制先把这款插件的安装验证、事件配置、轮转参数走一遍,再跑一次这个分析脚本确认日志真的有内容可读,而不是接了线没通电。希望帮到你。
本文还有配套的精品资源,点击获取