数据库巡检最容易被忽视的往往不是报错和告警,而是那些“看起来正常”的隐性风险。很多故障在爆发前几周甚至几个月,就已经在系统表、慢日志、锁等待和磁盘增长曲线里留下了痕迹,只是常规巡检脚本只盯着连接数、CPU和磁盘使用率,根本触达不到这些层面。真正的定时巡检,应该把重心放在“异常模式识别”上,而不是“指标是否超过阈值”。
一、先查“慢查询”背后的执行计划漂移,而不是只看慢日志条数
慢日志数量没有增加,不代表SQL没有变慢。同一个SQL语句,因为数据量增长、统计信息过期或者索引碎片化,执行计划可能悄悄从索引查找变成了全表扫描。这种变化不会立刻触发慢查询阈值,但会让单次查询从几毫秒涨到几百毫秒,等业务感知到的时候,数据库已经扛了很久。巡检时应该定期抓取pg_stat_statements或sys.dm_exec_query_stats中执行时间排名前二十的SQL,对比历史基线。重点看平均执行时间、执行次数、读取块数这三个指标是否出现背离。比如执行次数没变,但平均逻辑读翻倍,基本可以判定执行计划出了问题。这时候不要急着加索引,先更新统计信息或者重新编译执行计划,很多问题在这一步就能解决。
二、锁等待的“排队深度”比死锁更值得关注
死锁会报错,开发马上能发现,但锁等待是静默的。一个事务持有行锁不释放,后面的事务一个接一个排队,数据库连接池被慢慢占满,最终表现为“数据库连不上”或者“接口超时”。巡检时不能只看当前有没有锁等待,要看锁等待的平均时长和最大等待时长。MySQL里可以查performance_schema.data_lock_waits,SQL Server里看sys.dm_os_waiting_tasks。如果发现某些表的锁等待次数在特定时间段集中出现,要结合业务代码去定位是不是有事务在循环里提交、或者有未关闭的连接持有锁。一个很实用的判断方法:如果锁等待平均时长超过业务单次事务允许时间的30%,这个隐患就必须立即处理。
三、索引“膨胀”和“失效”要分开对待
很多巡检脚本只统计索引数量,不分析索引质量。一个表上有十几个索引,真正被查询优化器用到的可能只有三四个,剩下的全是维护负担。每次插入、更新、删除都要维护这些无用索引,写放大严重,还会占用大量缓冲池。巡检时应该定期统计每个索引的使用情况,比如MySQL的sys.schema_unused_indexes,或者PostgreSQL的pg_stat_user_indexes里idx_scan长期为零的索引。但要注意,有些索引是为月度报表或者季度对账准备的,不能简单删掉。正确做法是标记出来,和业务方确认后,要么删除,要么改成部分索引或者表达式索引。同时还要检查索引碎片率,B+树索引页面分裂过多会导致范围扫描效率急剧下降,尤其是自增主键之外的二级索引。
四、磁盘增长的“加速度”比剩余空间更致命
磁盘还剩20%不代表安全。如果这20%是在一周内从50%掉下来的,那留给你的反应时间可能只有几天。巡检要记录每次磁盘使用量的变化,计算增长速率。特别是大表的空间增长,比如日志表、流水表、消息表。用information_schema.tables或者pg_relation_size定期采集表大小,做成趋势图。一旦发现某张表的大小增长曲线突然变陡,要立刻检查是不是有批量导入任务、是不是有程序在循环写临时数据、是不是有未清理的历史数据堆积。很多数据库故障的直接原因不是性能差,而是磁盘被一张失控的表撑爆,连带着整个实例不可用。
五、连接数的“瞬时峰值”和“平均水位”要分开监控
平均连接数很低,不代表没有连接风暴。有些业务在整点或者特定活动时会瞬间拉起几百个连接,然后又迅速释放。如果监控粒度是分钟级,这种瞬时峰值很可能被平均掉。但连接风暴对数据库的冲击是真实的,短时间内大量连接建立和销毁,会耗尽线程资源,甚至触发操作系统的文件描述符上限。巡检时应该记录连接数的最大值和最小值,而不仅仅是平均值。如果发现最大值频繁接近max_connections,即使平均值只有30%,也要提前扩容连接池或者优化业务端的连接复用策略。另外要关注那些“sleep”状态但长时间不释放的连接,它们往往是应用代码里忘记关闭连接导致的泄漏。
六、备份有效性必须定期做“恢复演练”来验证
备份文件存在,不代表能恢复。很多团队每天做备份,但从来没真正恢复过,等到需要用时才发现备份文件损坏、备份不完整、或者恢复流程有问题。巡检不能只检查备份任务是否成功,还要定期抽一个备份文件做实际恢复测试。恢复到一个临时环境,验证数据完整性和一致性。特别是增量备份和日志备份的链条是否完整,如果中间断了一个日志备份,后面的所有增量都作废。这个测试不需要每天做,但至少每个月做一次,而且要有记录。恢复演练中发现的问题,往往比性能问题更致命,因为它是“用的时候才发现不能用”。
七、参数配置的“隐性漂移”要纳入版本管理
数据库参数不是一成不变的。有时候DBA临时调了一个参数解决某个问题,事后忘记改回来;有时候新版本升级后,某些参数的默认值变了;有时候运维自动化脚本会覆盖手工配置。这些变化如果不记录、不对比,就会成为隐性隐患。巡检时应该把当前参数配置和基线配置做diff,任何差异都要有明确的变更记录。重点关注连接数、缓冲池大小、日志文件大小、超时时间、字符集、事务隔离级别这些核心参数。一个典型的案例是:开发环境为了调试方便把事务隔离级别改成了READ UNCOMMITTED,后来通过某种方式同步到了生产环境,导致出现脏读,业务数据错乱,排查了很久才发现是参数被改了。
八、统计信息“过期”比缺失更危险
统计信息完全缺失时,优化器会选择一种保守的执行计划,虽然不一定最优,但通常不会太离谱。真正危险的是统计信息过期,优化器基于过时的数据分布做出判断,选了一个看似合理但实际非常糟糕的执行计划。比如一张表原本只有几千行,统计信息里记录的行数也是几千,但实际已经增长到几千万行,优化器还认为全表扫描代价很低,结果一个查询扫了几千万行数据。巡检时要检查统计信息的最后更新时间,对于数据变化频繁的表,要确保统计信息更新频率跟得上数据变化速度。可以开启自动更新统计信息,但对于大表,自动更新的触发阈值可能不够灵敏,需要手动定期更新或者提高采样率。
九、临时表和临时文件的“堆积”会拖垮整个实例
复杂查询、排序、分组操作会用到临时表或者临时文件。如果这些临时对象没有及时清理,会占用大量磁盘空间和内存。MySQL的tmpdir目录、PostgreSQL的temp_file_limit、SQL Server的tempdb,都是需要重点巡检的地方。特别是tempdb,如果它所在的磁盘和数据库文件在同一块盘上,tempdb膨胀会直接挤压数据文件的可用空间。巡检时要记录临时文件的最大大小和增长趋势,如果发现临时文件频繁接近上限,说明有大量复杂查询在同时运行,需要优化SQL或者增加内存来减少磁盘临时表的使用。另外要检查是否有未关闭的游标或者事务导致临时表无法释放。
十、日志文件的“写入延迟”和“切换频率”是性能的晴雨表
事务日志的写入速度直接决定了数据库的写入性能。如果日志写入延迟突然升高,说明磁盘I/O出现了瓶颈,或者日志文件所在磁盘和其他热点文件产生了争用。巡检时要监控日志写入的平均延迟和最大延迟,以及日志文件的切换频率。日志切换太频繁,说明日志文件设置得太小,每次切换都会触发检查点,导致性能抖动。日志切换太少,说明日志文件很大,但一旦需要恢复,恢复时间会很长。要根据业务写入量合理设置日志文件大小,让切换频率保持在每小时几次到每天几次之间。同时要检查日志文件所在磁盘的队列深度,如果队列深度持续大于1,说明磁盘已经跟不上写入速度。
十一、权限和账号的“僵尸权限”要定期清理
数据库账号权限管理混乱是很多安全问题的根源。有些账号是临时给开发人员用的,用完没有回收;有些账号的权限过大,比如直接授予了SUPER或者sysadmin;有些账号的密码长期不换,甚至还是默认密码。巡检时要列出所有账号及其权限,和业务方确认哪些账号还在使用。对于长期没有登录记录的账号,要标记出来并禁用。对于权限过大的账号,要按照最小权限原则重新分配。特别是那些具有批量删除、修改表结构、导出数据权限的账号,要重点审查。另外要检查是否有账号可以从任意主机登录,如果有,必须限制到具体的IP段。
十二、主从复制的“延迟波动”比平均延迟更值得警惕
主从复制延迟是常见问题,但平均延迟低不代表没有问题。如果延迟在某个时间段突然飙高,然后又恢复正常,说明有批量任务或者大事务在周期性冲击从库。这种波动会导致从库数据不一致的时间窗口拉长,如果这时候发生主库故障,切换到从库就会丢失这部分数据。巡检时要记录复制延迟的最大值和最小值,以及延迟超过阈值的时间段分布。对于周期性出现的延迟高峰,要找到对应的批量任务,调整执行时间或者拆分成小事务。另外要检查从库的SQL线程是否在持续运行,有没有因为某个错误而停止,比如主键冲突、数据类型不匹配等。这些错误如果不及时处理,从库会越落后越多。
十三、表结构和数据类型的“隐性坑”要提前发现
有些表结构设计上的问题,在数据量小的时候完全看不出来,一旦数据量上来就会爆发。比如用VARCHAR存数字,导致排序和比较时无法使用索引;用TEXT存JSON,导致查询时无法利用JSON函数;用自增ID做主键,但在分布式环境下产生冲突;没有设置合适的字符集,导致中文乱码或者索引失效。巡检时要定期检查表结构,重点关注那些数据量增长快、查询频繁的表。检查字段类型是否合理、是否有隐式类型转换、是否有过长的索引前缀、是否有缺失的外键约束。这些问题不会立刻导致故障,但会在某个时间点集中爆发,而且排查起来非常困难。
十四、定时巡检本身也要“防呆”和“留痕”
巡检脚本写得再好,如果执行失败没人发现,等于没有巡检。巡检任务本身要有监控,比如巡检脚本执行失败要发告警,巡检结果要存档,关键指标要形成趋势图。不能只靠人肉去跑脚本,然后看一眼就关掉。要把巡检结果结构化,存到数据库或者文件里,定期对比历史数据,自动发现异常波动。同时要保留巡检的原始输出,方便事后追溯。很多故障复盘时发现,其实巡检脚本早就发现了异常,但因为结果没有留痕,或者告警被忽略,导致错过了最佳处理时机。巡检的最终目的不是“发现问题”,而是“在问题变成故障之前解决它”。
