数据库巡检这种事,平时看着不起眼,真到了月底季末要汇总报告的时候,能把人折腾到怀疑人生。我从裸写SQL到后来做自动化巡检,中间踩了不少坑,今天就把这套“数据库巡检Word报告一键生成”的完整思路和落地步骤分享出来,专治各种手工整理报告的低效问题。
开头先给结论:这套方案的核心就三步——用Linux命令批量采集数据库巡检项、用Python脚本做数据清洗与分析、用python-docx库把结果渲染成带格式的Word报告。整个过程跑一遍大概几分钟,比起以前手动连库执行、逐条复制粘贴、再手工调格式,效率提升得不是一点半点。
1. 整体设计与思路拆解:怎么把“巡检报告”变成流水线作业
1.1 手工巡检报告为什么那么痛苦
我以前做数据库巡检,流程是这样的:先打开运维平台,挨个连上数据库实例,执行十几条巡检SQL,把结果一条条复制到Excel里,然后在Excel里做格式调整。这还算好的,遇到多实例的环境,光是在不同服务器之间来回切换就能耗掉大半天。最后还要对着Excel复制到Word里,调整字体、对齐方式、页码,一套下来人基本处于半崩溃状态。
更头疼的是,巡检SQL的粒度特别碎。比如检查连接数、检查表空间使用率、检查慢查询、检查主从延迟,这些都是独立的SQL,结果格式还不一样。有的查出来是一行记录,有的查出来是十几行列表,手工整理的时候很容易看串行。
所以从一开始我的思路就很明确:巡检报告生成的痛点不在“能不能查”,而在“查完之后怎么整理”。与其每次手工整理,不如把这套流程固化成脚本,让机器替我干活。
1.2 方案选型:为什么选Linux命令 + Python + Word这套组合
这个方案不是一上来就选定的,中间也试过用Shell脚本直接处理,试过用监控平台导出报告,最后才定下来用Linux命令采集、Python处理、Word输出这条组合链路。
Shell脚本直接生成Word报告,不是不行,但样式控制太弱。用printf拼接HTML再转Word,做出来的报告排版粗糙,遇到中文缩进、表格列宽调整就特别费劲。监控平台自带的报告导出功能,虽然能自动汇总,但是格式固定,没法按我自己的巡检模版来定制,而且平台没覆盖到的巡检项还得回退到手工处理。
Python + python-docx这套组合最大的优势是:数据采集可以完全复用Linux的原生命令和脚本逻辑,数据处理用Python写起来又快又灵活,最后生成Word报告时可以直接操纵文档结构——标题、段落、表格、字体、颜色都能做到精确控制。换句话说,就是把“采集”和“展示”两个阶段彻底分开,每个阶段用最擅长的工具去做,互不拖累。
方案确定之后,数据库巡检的核心就变成了三个环节:一是巡检项数据的标准化采集,二是数据的清洗与落库,三是报告的自动生成。所有的工作都围绕这三件事展开。
2. 核心细节解析与实操要点:巡检项采集与数据处理
2.1 巡检项采集:不是所有数据都要进报告
很多同学做巡检报告犯的一个通病,是想把数据库的状态参数全塞进报告里,结果报告写得比操作手册还厚,读的人根本抓不住重点。我做了这么多轮巡检之后,总结出来一份核心巡检项清单,基本上覆盖了日常运维的大部分需求:
- 数据库实例基础信息:版本号、运行时长、字符集、数据库模式
- 资源使用情况:CPU使用率、内存使用率、磁盘空间、IO使用情况
- 连接与会话:当前连接数、最大连接数、活跃会话数、阻塞会话数
- 性能指标:慢查询数量、缓存命中率、锁等待次数、事务提交回滚比
- 数据库对象健康度:表空间使用率(非常重要)、碎片率、索引失效数量
- 备份与日志:最近一次备份时间、备份是否成功、错误日志条数与最近内容
- 主从复制状态:复制线程是否正常、延迟时间(秒)
这些巡检项,有些是直接执行SQL就能拿到的,比如连接数、数据库版本这些,有些需要从操作系统层面拿,比如CPU和磁盘状态。所以在采集阶段,我的做法是分成两条线并行:
- 操作系统层面的指标,用一段Linux脚本统一采集,输出到结构化文本文件
- 数据库层面的指标,用一组固定的SQL脚本采集,结果也导出为统一的CSV格式
两条线的数据最后汇总到同一个工作目录下面,Python脚本统一读取。这样做的好处是采集过程对数据库的影响极小,而且所有原始数据都有留底,报告生成完之后如果发现某个指标异常,还能回头翻原始数据核对。
2.2 Linux一键获取文件名并生成列表的妙用
这里就不得不提最近很火的那个技巧:linux0系统下一键获取文件名并生成列表。这个操作在数据库巡检场景里简直是刚需。
怎么理解?因为巡检脚本要处理的文件特别多,可能是几十个实例的巡检结果文件,文件的命名规则又带着日期后缀。手工把这些文件名一个一个打出来管理,既容易漏,又容易错。而用Linux命令一键获取当前目录下所有文件名的列表,再用脚本去逐个读取处理,整个流程就顺滑了。
实际命令很简单,一条find指令就能完成:
find /var/dbcheck/ -name "*.csv" -type f | sort > filelist.txt如果文件名带日期,需要按日期筛选,可以这样写:
find /var/dbcheck/ -name "*$(date +%Y%m%d)*.csv" -type f | sort > filelist.txt这个filelist.txt文件就是后续Python脚本读取所有数据文件的索引清单。核心价值在于:巡检实例的增删不会影响主脚本逻辑,每次跑批前自动重新扫一遍文件列表就能拿到最新情况,完全不用手工维护文件清单。
再延伸一步,如果想把巡检结果的文件命名做得更规范,可以直接把采集命令和文件名生成放在一起:
result_dir=/var/dbcheck/$(date +%Y%m%d) mkdir -p $result_dir db_list=(db01 db02 db03) for db in ${db_list[@]}; do mysql -h $db -uroot -p****** -e "show global status;" > ${result_dir}/${db}_status.csv mysql -h $db -uroot -p****** -e "show variables;" > ${result_dir}/${db}_variables.csv done这样跑一次巡检,一个以日期命名的目录下就整整齐齐地躺着所有实例的巡检结果,文件名自带库名和指标类型,配合find命令生成的文件列表,整个数据采集阶段就齐活了。
注意:生产环境执行SQL采集时,尽量不要用root账号。巡检脚本用的账号只需要具备查询权限就行,可以通过MySQL的GRANT语句单独创建。巡检脚本本身最好也放在独立的服务器上执行,避免直接在数据库主机上跑脚本对生产环境造成风险。
2.3 数据处理:让Python帮你完成脏活累活
数据采集回来之后,面临的第一个问题就是格式不统一。MySQL命令行导出的CSV是竖排格式,操作系统层面的vmstat、iostat输出又是另外一套对齐方式,这两个要合并到同一个报告里,必须做数据清洗。
我清洗的原则是三步走:
第一步,去噪。把空行、注释行、命令本身的提示信息全部过滤掉。MySQL执行SQL时经常会在结果前面带一段警告信息,这些杂讯不处理干净,后面解析必定出错。
第二步,标准化。不同的巡检脚本导出的字段名可能不大一样,比如有的叫Threads_connected,有的叫threads_connected,大小写不一致、下划线不一致,要在这一步统一成标准字段名。
第三步,落库。清洗完的数据按巡检日期、实例名、指标名三个维度存储,方便后续查历史数据做趋势分析。
这一步实际上积累下来的经验是:清洗逻辑尽量简单直接,不要在一开始就追求把所有指标都清洗到位。先跑通主流程,再逐步补充清洗规则,这样调试起来效率更高。
3. 实操过程与核心环节实现:生成Word报告的完整链路
3.1 环境准备:安装依赖库
生成Word报告,Python里最常用的库是python-docx,它能直接操纵Word文档的段落、表格、样式,功能非常完整。另外还需要pandas做数据处理,需要openpyxl作为pandas的Excel引擎(如果涉及Excel读写的话)。
pip install python-docx pandas openpyxl如果环境是离线的内网环境,可以提前下载好whl包,用pip离线安装。这里有一个小坑:python-docx对Python版本有一定要求,3.8以下的版本可能装不上最新版,内网环境如果Python版本比较老,建议先确认一下版本兼容性。
提示:离线安装时,除了python-docx本身,它的依赖库lxml也要一并下载,否则安装会报错。用pip download python-docx --no-deps可以把主库拉下来,再用pip download lxml拉依赖。
3.2 报告模板设计:结构决定阅读体验
我在做自动化报告之前,先花了一晚上手工做了一份报告模板,确定了巡检报告的结构。这份模板后来成了脚本生成的蓝本。我的报告结构是这样的:
封面页:报告标题、巡检时间段、实例数量、报告生成时间、生成方式 概述页:本次巡检的整体结论,包括发现的异常项数量、需要关注的风险点 实例详情页:每个实例一段,包含基础信息、资源使用、会话与性能指标 异常项汇总页:把所有实例的异常指标集中列出来,按严重程度排序 附录:包含本次巡检使用的SQL脚本说明、采集命令、数据留底位置
这个结构里,最核心的是异常项汇总页。业务方和领导拿到报告,第一眼看的绝对不是哪个实例的缓存命中率是多少,而是“这周有没有出问题、有哪些风险点”。所以异常项汇总必须放在实例详情前面,让人一眼就能看到结论。
3.3 核心代码:从数据文件到Word报告
下面这段代码就是整个“一键生成”的核心逻辑。注释写得比较细,照着改一下文件路径就能直接用:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import datetime import pandas as pd from docx import Document from docx.shared import Pt, Cm, RGBColor from docx.enum.text import WD_ALIGN_PARAGRAPH from docx.enum.table import WD_TABLE_ALIGNMENT from docx.oxml.ns import qn # 配置区域 DATA_DIR = "/var/dbcheck/" + datetime.datetime.now().strftime("%Y%m%d") OUTPUT_FILE = "/var/dbcheck/数据库巡检报告_{}.docx".format( datetime.datetime.now().strftime("%Y%m%d") ) # 读取文件名列表 def get_filelist(): """用 find 命令生成的文件清单,读取所有 csv 文件的绝对路径""" filelist = [] with open(os.path.join(DATA_DIR, "filelist.txt"), "r") as fp: for line in fp: line = line.strip() if line.endswith(".csv"): filelist.append(line) return filelist # 设置正文中文字体 def set_cn_font(paragraph, font_name="微软雅黑", font_size=10.5, bold=False, color=None): for run in paragraph.runs: run.font.name = font_name run._element.rPr.rFonts.set(qn("w:eastAsia"), font_name) run.font.size = Pt(font_size) run.font.bold = bold if color: run.font.color.rgb = RGBColor(*color) # 生成报告的封面和概述 def build_report_header(doc, db_count, check_date): p_title = doc.add_heading("数据库巡检报告", level=0) p_title.alignment = WD_ALIGN_PARAGRAPH.CENTER info = doc.add_paragraph() run = info.add_run("巡检日期:{}\n实例数量:{}\n报告生成时间:{}".format( check_date, db_count, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") )) set_cn_font(info, font_name="微软雅黑", font_size=12) doc.add_page_break() # 为每个实例生成详情表格 def build_db_detail(doc, db_name, df): doc.add_heading(db_name, level=2) # 基础信息表 basic_cols = ["变量名", "值"] df_basic = df[df["指标类型"] == "variables"][["变量名", "值"]] table = doc.add_table(rows=1, cols=2, style="Light Grid Accent 1") table.alignment = WD_TABLE_ALIGNMENT.CENTER hdr = table.rows[0].cells hdr[0].text = "配置项" hdr[1].text = "当前值" for _, rowdata in df_basic.iterrows(): row = table.add_row().cells row[0].text = str(rowdata["变量名"]) row[1].text = str(rowdata["值"]) # 状态指标表 doc.add_heading("运行状态指标", level=3) df_status = df[df["指标类型"] == "status"][["变量名", "值"]] table2 = doc.add_table(rows=1, cols=2, style="Light Grid Accent 1") hdr2 = table2.rows[0].cells hdr2[0].text = "状态项" hdr2[1].text = "当前值" for _, rowdata in df_status.iterrows(): row = table2.add_row().cells row[0].text = str(rowdata["变量名"]) row[1].text = str(rowdata["值"]) # 主流程 def main(): if not os.path.exists(DATA_DIR): print("数据目录不存在,请先执行采集脚本:{}".format(DATA_DIR)) return filelist = get_filelist() if not filelist: print("文件列表为空,请检查 filelist.txt 是否生成") return doc = Document() # 设置默认样式 style = doc.styles["Normal"] style.font.name = "微软雅黑" style.element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑") style.font.size = Pt(10.5) build_report_header(doc, len(filelist), datetime.datetime.now().strftime("%Y-%m-%d")) for csv_file in filelist: db_name = os.path.basename(csv_file).split("_")[0] try: df = pd.read_csv(csv_file) build_db_detail(doc, db_name, df) except Exception as e: print("解析文件失败:{},原因:{}".format(csv_file, e)) doc.save(OUTPUT_FILE) print("报告已生成:{}".format(OUTPUT_FILE)) if __name__ == "__main__": main()这套脚本跑一次,Word文档就自动生成了。里面用到的表格样式叫“Light Grid Accent 1”,效果是浅灰色网格带蓝色表头,打印出来也很清晰。如果不想用这个样式,可以直接把style参数改成“Table Grid”,就是纯黑色网格线,比较中规中矩,看个人喜好。
3.4 一键运行:把采集和报告生成串起来
前面的代码是报告生成部分,但“一键生成”要的是从头到尾一条命令跑完。我用了一个Shell脚本把整个流程串起来:
#!/bin/bash # db_check_auto.sh - 数据库巡检一键采集+报告生成脚本 today=$(date +%Y%m%d) result_dir="/var/dbcheck/${today}" mkdir -p ${result_dir} echo "============== 1. 采集数据库巡检数据 ==============" # 这里执行你的采集命令,按实例批量导出CSV bash /opt/scripts/db_collect.sh ${result_dir} echo "============== 2. 生成文件名列表 ==============" find ${result_dir} -name "*.csv" -type f | sort > ${result_dir}/filelist.txt echo "============== 3. 生成Word巡检报告 ==============" python3 /opt/scripts/gen_report.py echo "============== 4. 完成 ==============" ls -lh ${result_dir}/*.docx整个流程跑下来,速度取决于实例数量和巡检项多少。我这边大概40个实例,从采集到报告生成完成,整体耗时在5分钟以内,其中绝大部分时间是花在连接数据库执行查询上,报告生成本身不到十秒。
4. 常见问题与排查技巧实录:这堆坑我替你踩过了
4.1 中文乱码问题
Word报告里最常遇到的就是中文乱码。这个问题的根源在python-docx默认字体对中文支持不好上,设置字体时需要同步设置eastAsia字体,否则中文显示就变成方块。代码里我已经在set_cn_font函数里做了处理,核心这行不能漏:
run._element.rPr.rFonts.set(qn("w:eastAsia"), font_name)另外,CSV文件编码不一致也会导致乱码。从MySQL导出的CSV默认可能是latin1编码,而Python读取时如果按utf-8解析,中文就会全部变成乱码。解决方法是读取CSV时统一指定编码,或者先用file命令检查文件编码:
file db01_status.csv如果是latin1编码,pandas读取时指定encoding="latin1"即可。
4.2 文件列表为空导致报告生成中断
这个问题多半是find命令的路径写错了。我最初遇到一次,排查了半天,最后发现是文件路径中带了空格,导致find的匹配出问题。解决方案是在find命令中给路径加双引号:
find "${result_dir}" -name "*.csv" -type f | sort > "${result_dir}/filelist.txt"另外一个容易被忽略的点是:脚本中写死的日期和实际数据目录对不上。比如你前一天把采集脚本挂到crontab里,到了凌晨跑批时日期已经变了,但脚本里还引用着旧的日期路径,就会导致文件列表为空。这个可以通过把所有日期变量统一用$(date +%Y%m%d)动态生成来解决,也就是上面代码里已经采用的方案。
4.3 生成的Word表格列宽不均
python-docx生成的表格默认是自动调整列宽的,但在中文长文本下容易出现一列特别宽、一列特别窄的情况。处理办法是手动设置表格各列的宽度:
from docx.shared import Cm table.columns[0].width = Cm(6) table.columns[1].width = Cm(8)需要注意的是,列宽设置要在填写完数据之后再进行,否则有时会被单元格内容撑开。
4.4 数据库连接超时导致采集失败
采集脚本连数据库时,如果网络延迟高或者目标库有负载,很容易出现连接超时。我的做法是:在采集脚本里设置连接超时和SQL执行超时时间,并加入失败重试机制:
mysql -h $db -uroot -p****** --connect-timeout=10 --default-character-set=utf8 -e "show global status;" || { echo "连接失败: $db, 等待5秒重试" sleep 5 mysql -h $db -uroot -p****** --connect-timeout=10 --default-character-set=utf8 -e "show global status;" }如果重试还失败,就把这个实例标记为采集异常,记录下来,报告里单独体现,不让整个巡检流程因为这个实例卡住。
4.5 巡检SQL执行大量数据导致数据库负载升高
在线上的生产库执行巡检SQL,最怕一个不小心拖垮数据库。我踩过一次坑,当时查某张超大表的统计信息,直接导致数据库IO飙高,业务方很快电话就过来了。
从那以后,巡检SQL就统一规约:
- 所有查询都走information_schema,不去业务表里做count(*)
- 复杂查询一律加limit限制返回行数
- 大库巡检错峰执行,避开业务高峰期
- 每一条巡检SQL都先explain确认执行计划,杜绝全表扫描
同时要注意,information_schema本身也不是绝对安全的。查这张视图有时候也会触发行数统计逻辑,对大表还是会有成本。所以更稳妥的做法是:直接查数据库的状态表和统计表,不要对业务数据做任何操作。
4.6 快速排查技巧速查表
| 故障现象 | 可能原因 | 解决动作 |
|---|---|---|
| Word中中文变方块 | python-docx未设置eastAsia字体 | 在代码中同时设置w:eastAsia字体 |
| 报告内容为空 | filelist.txt路径错误 | 检查find命令的路径和日期变量 |
| CSV字段解析错位 | 分隔符或编码不一致 | 用file命令查看文件编码,指定编码读取 |
| 表格列宽异常 | 未手动设置列宽 | 用columns[index].width设置列宽 |
| 采集脚本卡住 | 实例连接超时无限制 | 添加连接超时参数,增加失败重试 |
| 数据库负载飙升 | 巡检SQL扫描了大表 | 优化SQL,限制返回行数,错峰执行 |
4.7 数据记录留痕:报告不是终点
报告生成出来,不是整个巡检流程的终点。我的习惯是,每次生成的Word报告和采集的CSV原始数据都按日期归档到统一的巡检数据目录下,保留至少六个月。这样做有两个好处:一是出问题时可以回溯历史数据,对比某个指标在什么时间点开始变的异常;二是季度的趋势分析报告直接基于这些历史数据出,不用再翻旧账。
归档目录结构大致是这样的:
/var/dbcheck/ ├── 20250101/ │ ├── db01_status.csv │ ├── db01_variables.csv │ ├── ... │ ├── filelist.txt │ └── 数据库巡检报告_20250101.docx ├── 20250108/ └── ...再用一条命令压缩归档:
tar -czf /var/dbcheck/backup/dbcheck_$(date +%Y%m%d).tar.gz ${result_dir}这一个归档动作,建议也加到Shell脚本里,跟着主流程自动执行。
5. 进阶优化与扩展思路:这套脚本还能干更多事
5.1 自动发送邮件通知
Word报告生成之后,如果还需要人工下载再发给别人,还谈不上真正的“一键”。可以加上邮件自动发送的环节。用Python的smtplib把生成的Word报告作为附件发出去:
import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.application import MIMEApplication def send_mail(receivers, subject, body, attachment_path): msg = MIMEMultipart() msg["From"] = "dba@example.com" msg["To"] = ",".join(receivers) msg["Subject"] = subject msg.attach(MIMEText(body, "plain", "utf-8")) with open(attachment_path, "rb") as f: part = MIMEApplication(f.read()) part.add_header("Content-Disposition", "attachment", filename=os.path.basename(attachment_path)) msg.attach(part) smtp = smtplib.SMTP("smtp.example.com", 25) smtp.sendmail("dba@example.com", receivers, msg.as_string()) smtp.quit()邮件标题和正文文本里,可以顺带把本次巡检的异常项摘要带上。这样领导只需要打开邮件扫一眼摘要,就能了解巡检的基本情况,不需要打开Word附件。
5.2 异常自动判定与告警分级
在“数据清洗”这一步里,可以加一个异常判定逻辑。比如表空间使用率超过85%自动标黄,超过95%自动标红,连接数达到上限的80%判定为告警。把这些判定规则做成一个阈值配置文件,调整阈值时不用改代码,只改配置就能上线:
rules: - metric: "tablespace_usage" warning: 85 critical: 95 - metric: "threads_connected" warning: 80 critical: 90Python脚本读取这个配置文件,逐项比对巡检数据,自动在报告里生成异常项汇总和风险分级,同时把异常项的明细单独写到一份告警清单里,给邮件发送模块使用。这就是从“传统巡检”向“智能巡检”演进的起点。
5.3 接入监控平台API
如果你们的数据库实例是运行在云平台上的,很多指标(比如CPU使用率、网络流量等)可以直接通过平台提供的API获取,替代掉一部分手工采集。这类API一般走HTTP接口,返回JSON数据,Python脚本里直接用requests库就能拉取。接入之后,采集环节的稳定性会提高不少,毕竟云平台的监控数据比自己在实例内部采集更全面,也不用担心巡检SQL对数据库的影响。
实际操作中,我给所有实例做了一次API接入,把平台的CPU、内存、网络指标和实例内部的连接数、慢查询、锁等待等指标整合到同一份报告里,效果比单靠巡检SQL采集要立体得多。
5.4 巡检报告的版本管理与历史对比
当报告按月生成之后,可以做一份历史对比页,把每个实例的“本月”和“上月”关键指标放在一起,一眼就能看出哪些指标在恶化。这个功能的实现并不复杂,就是从历史CSV目录里读取上个月同一时间的数据,和本月数据做一次差值计算,然后渲染到Word表格里。但它带来的价值很明显——运维报告从“记录当下”变成了“预测趋势”,给做容量规划和性能优化提供了有力的数据支撑。
结尾:这套方案给我带来了什么变化
从我自己的实际使用体验来看,数据库巡检报告自动化之后,最大的变化不是“省时间”这么简单,而是整个巡检工作从“事务型工作”变成了“分析型工作”。以前巡检的时间都花在复制粘贴和调整格式上,现在这些时间全部节省下来,可以真正去思考巡检数据背后的含义——为什么这个库的连接数持续上涨、为什么那个表的碎片率降不下来。
这中间也踩过不少坑,尤其是最开始那版脚本,直接在report生成阶段才去读数据库,结果报告生成速度和数据库性能互相拖累,后来改成“采集与生成分离”才彻底解决问题。所以如果你准备动手,我的建议很直接:先做好数据采集这个基础环节,把数据留痕做好,报告生成只是最后一公里的展示问题,前期数据扎实了,后面怎么做都顺。
还有一点想特别提醒:自动化的边界要清楚。报告生成可以自动化、数据采集可以自动化,但巡检结论的判断和风险决策,必须有DBA的经验介入。自动化是帮我们节省体力劳动、放大专业判断的工具,不是取代专业判断的机器。希望这套思路能帮到你,做运维的都不容易,能少加一次班是一次。