数据库访问频次失控是很多业务系统在高并发或遭遇恶意攻击时最先暴露的问题。攻击者往往不需要复杂的SQL注入手段,仅通过高频请求就能耗尽数据库连接池、拖垮CPU和IO,最终导致整个服务不可用。管控数据库访问频次,本质上是把“谁在什么时间、以什么频率、访问了什么数据”纳入可控范围,并在异常发生时自动拦截或降级。
先要明确一个前提:频次管控不是简单地限制每秒查询数。它需要区分正常业务高峰与恶意流量、区分不同账号和接口的合理访问模式、区分读操作与写操作的风险等级。一个只设置全局阈值的方案,很容易误伤正常用户,或者被攻击者用分布式请求绕过。真正有效的管控,必须建立在多维度的访问特征识别和分层限流机制上。
从连接层开始限制,防止资源被瞬间占满数据库连接池是应用与数据库之间的第一道闸门。攻击者发起大量半连接或慢查询时,如果连接池没有合理的获取超时和最大活跃数限制,正常业务请求会排队等待,最终形成雪崩。建议在连接池配置中明确设置最大连接数、最大等待时间、连接泄漏回收时间和空闲连接回收策略。例如,在Java应用使用HikariCP时,可以这样配置:
maximumPoolSize=50 connectionTimeout=3000 idleTimeout=600000 maxLifetime=1800000 leakDetectionThreshold=10000
连接池的最大连接数要根据数据库实际能承载的并发连接来设定,而不是越大越好。MySQL默认最大连接数通常为151,如果应用层连接池设置过大,多个应用实例叠加后很容易超过数据库上限。连接超时时间建议设置在2到5秒之间,让请求快速失败并返回友好提示,而不是长时间占用线程等待。
基于账号和IP的双维度频次统计数据库账号是识别访问主体的关键维度。生产环境中应当为不同的业务模块分配独立的数据库账号,而不是所有服务共用一个高权限账号。这样一旦某个模块被攻击或出现异常,可以通过账号维度快速定位并限制其访问频次。对于每个账号,可以统计其在固定时间窗口内的SQL执行次数、返回行数、消耗的CPU时间等指标。
IP维度的频次统计同样重要,尤其是面向公网的应用。攻击者可能使用代理池轮换IP,但短时间内同一IP发起的高频数据库操作仍然是可以捕捉的特征。可以在应用层或数据库代理层记录每个IP在10秒、1分钟、5分钟内的请求次数,超过阈值后触发验证码、延迟响应或直接拒绝。
一个常见的实践是在应用层使用滑动窗口算法统计访问频次。滑动窗口比固定窗口更能平滑地反映实时流量,避免固定窗口边界处的突发请求绕过限制。实现上可以使用Redis的ZSET结构,以时间戳为score,每个请求记录一条,统计时删除窗口外的记录并计算剩余数量。
SQL级别限流:识别高成本查询并单独管控并非所有SQL的访问成本都一样。一条全表扫描的SELECT语句可能比几十条主键查询消耗更多资源。频次管控不能只看执行次数,还要结合SQL的执行成本。数据库自带慢查询日志和性能视图,可以定期分析慢SQL,将高成本SQL提取出来,在应用层或中间件层单独设置更严格的访问频次。
对于MySQL,可以开启performance_schema和sys库,通过以下语句找出执行次数多且平均耗时长的SQL:
SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT FROM performance_schema.events_statements_summary_by_digest ORDER BY COUNT_STAR DESC LIMIT 20;
找到高成本SQL后,可以在应用层对这些SQL的调用点做专门的限流。例如,某个报表接口内部执行了一条复杂聚合查询,就对该接口设置每秒最多5次的限制,而不是对整个数据库账号做一刀切。这样既能保护数据库,又不会影响其他轻量查询的正常执行。
引入数据库代理层实现透明管控当应用系统较多、修改应用代码成本较高时,可以在应用与数据库之间部署数据库代理层,如ProxySQL、MaxScale或自研的中间件。代理层可以解析SQL协议,识别客户端IP、数据库账号、SQL指纹等信息,并在转发前进行频次判断。代理层的优势是对应用透明,不需要每个应用单独集成限流逻辑。
在ProxySQL中,可以通过mysql_query_rules表中的active、delay、error_msg等字段实现查询延迟和拒绝。例如,对某个账号的SELECT操作设置每秒最大100次,超过后延迟100毫秒响应,进一步超过则返回错误。代理层还可以统计每个SQL指纹的访问频次,将异常模式写入日志供后续分析。
动态阈值与自适应限流固定阈值最大的问题是无法适应业务波动。促销活动期间数据库访问量可能是平时的十倍,如果阈值设置过低会误杀正常流量,设置过高则在攻击面前形同虚设。动态阈值方案可以根据历史同期的访问量、当前系统负载、数据库连接数等指标自动调整限流参数。
一个实用的做法是结合数据库的Threads_running、Threads_connected和Innodb_row_lock_waits等状态指标,当这些指标接近危险水位时,自动收紧频次限制。例如,当Threads_running超过最大连接数的70%时,将非核心账号的访问频次降低到正常值的50%;超过90%时,只允许核心交易账号访问,其他账号排队或拒绝。
自适应限流需要监控系统配合,将数据库状态指标实时推送到限流决策模块。决策模块根据预设的规则计算出当前允许的最大频次,并下发到应用层或代理层。这种方案实施复杂度较高,但在核心交易系统中能显著降低数据库被打垮的风险。
写操作与读操作分离管控写操作的破坏性远大于读操作。恶意攻击者如果获得一个可执行INSERT、UPDATE或DELETE的账号,即使频次不高,也可能造成数据篡改或丢失。因此,频次管控必须对写操作设置更严格的阈值,并且对写操作的SQL内容进行额外校验。
在读写分离架构中,读操作走从库,写操作走主库。主库的频次管控应当更加严格,因为主库一旦过载,会直接影响所有从库的数据同步。建议对写操作实行白名单机制,只有经过审核的SQL模式才允许执行,其他写操作直接拒绝并记录告警。对于批量更新和删除,必须要求带LIMIT条件,防止误操作或恶意清空表。
利用数据库审计日志做离线分析在线限流能拦截大部分恶意请求,但攻击者可能会用低频慢速的方式绕过阈值。例如,每10秒请求一次,单看频次并不高,但持续数小时也会消耗大量资源。这类攻击需要通过离线分析数据库审计日志来发现。
审计日志记录了每条SQL的执行时间、来源IP、账号、SQL文本、返回行数等信息。通过分析这些日志,可以发现访问模式异常,比如某个账号在非工作时间频繁查询敏感表、某个IP在短时间内尝试访问多个不存在的表、某个SQL的执行次数在特定时间段出现异常峰值等。离线分析的结果可以反哺在线限流规则,形成闭环。
防止分布式高频攻击的联动策略攻击者使用大量代理IP或肉鸡发起分布式请求时,单IP的频次可能都不高,但整体请求量巨大。这种情况下,单纯依赖IP维度限流效果有限。需要结合设备指纹、用户行为特征、业务接口的语义校验等多维度信息进行识别。
在应用层,可以对每个用户会话绑定设备指纹,统计同一设备在多个IP上的访问频次。对于未登录的匿名请求,可以要求先通过验证码或行为验证,通过后才允许访问数据库相关接口。对于登录用户,可以结合账号的历史行为基线,当某个账号的访问模式突然变化时,触发二次验证或临时限制。
数据库访问频次管控不是单点防御,而是连接层、应用层、代理层、数据库层和离线分析层协同工作的体系。每一层都有其不可替代的作用,缺少任何一层都可能留下被攻击者利用的缺口。实施时需要根据自身业务特点选择合适的技术方案,先解决最紧迫的连接池耗尽和高频SQL问题,再逐步完善动态阈值和分布式攻击防御。最终目标是让数据库在任何流量形态下都能保持稳定运行,同时将误伤正常用户的概率降到最低。
