流量攻击,尤其是针对应用层的CC攻击和针对传输层的DDoS攻击,一旦到达数据库层面,表现形式不再是简单的带宽占满,而是数据库连接池被瞬间耗尽、查询线程阻塞、内存溢出甚至主从复制延迟断裂。处理这种场景,不能仅靠外围清洗,必须深入到数据库内核参数、架构拓扑和业务逻辑层进行联动防御。

当数据库遭遇流量攻击,最直观的现象是应用端报错“too many connections”或“connection timeout”。此时登录数据库查看,会发现大量处于“Sending data”或“Creating sort index”状态的慢查询堆积,CPU使用率飙升至100%,但磁盘IO可能并不高——这说明攻击流量触发了大量全表扫描或低效索引查询。针对这种情况,第一道防线不是扩容,而是立即开启数据库自带的连接防火墙机制。

以MySQL为例,第一时间执行以下命令限制最大连接数和单用户连接配额,防止一个恶意账号拖垮整个实例:

-- 紧急降低全局最大连接数,保留给管理员应急通道
SET GLOBAL max_connections = 200;
-- 限制单用户最大连接数,针对可疑账号直接降为0
ALTER USER 'suspect_user'@'%' WITH MAX_USER_CONNECTIONS 5;
-- 强制杀掉空闲时间过长但未释放的连接
SET GLOBAL wait_timeout = 60;
SET GLOBAL interactive_timeout = 60;

对于PostgreSQL,情况类似但机制不同。攻击流量往往导致大量连接处于“idle in transaction”或“active”状态。此时需要立即调整连接限制并终止恶意会话:

-- 查看当前连接数及状态
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;
-- 终止特定来源IP的所有连接
SELECT pg_terminate_backend(pid) FROM pg_stat_activity 
WHERE client_addr = '攻击源IP' AND pid <> pg_backend_pid();
-- 紧急限制最大连接数
ALTER SYSTEM SET max_connections = 100;
SELECT pg_reload_conf();

仅仅限制连接数还不够,攻击流量往往伪装成正常查询请求,比如针对搜索接口的模糊查询、针对报表系统的全表聚合操作。这时需要启用数据库的查询重写和资源控制模块。MySQL 5.7及以上版本可以通过设置max_execution_time来强制终止超时查询,避免一个慢查询占用资源过久:

-- 全局设置查询超时时间为5秒
SET GLOBAL max_execution_time = 5000;
-- 对于特定账号设置资源限制
ALTER USER 'api_user'@'%' WITH MAX_QUERIES_PER_HOUR 1000 
  MAX_UPDATES_PER_HOUR 100 MAX_CONNECTIONS_PER_HOUR 500;

在PostgreSQL中,可以通过statement_timeout参数实现类似效果,同时结合pg_stat_statements插件快速定位高频低效查询:

-- 设置语句超时时间为3秒
ALTER DATABASE your_db SET statement_timeout = '3s';
-- 查找执行次数最多且平均耗时较长的查询
SELECT query, calls, mean_time, total_time 
FROM pg_stat_statements 
ORDER BY calls DESC LIMIT 20;

当攻击流量特征明显,比如某个接口被高频调用、某个SQL模板反复出现,此时最有效的办法是在数据库代理层或中间件层实施SQL防火墙规则。以ProxySQL为例,可以动态创建查询规则拦截恶意SQL模式:

-- 拦截包含全表扫描关键词的查询
INSERT INTO mysql_query_rules (rule_id, active, match_pattern, 
  error_msg, action) 
VALUES (100, 1, 'SELECT * FROM large_table', 
  'Attack query blocked', 'error');
-- 限制特定接口的查询频率
INSERT INTO mysql_query_rules (rule_id, active, match_digest, 
  delay, max_delay) 
VALUES (200, 1, 'SELECT * FROM products WHERE', 1000, 5000);
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK;

如果架构中没有代理层,直接在应用代码层实现熔断和降级是最后一道防线。但这里重点讨论数据库层面的防护,所以更推荐在数据库连接池层面配置紧急限流。以HikariCP为例,在攻击发生时动态调整连接池参数:

// 紧急收缩连接池,将最大连接数从100降至20
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20);
config.setConnectionTimeout(2000); // 2秒获取不到连接即失败
config.setIdleTimeout(30000);      // 空闲连接30秒回收
config.setMaxLifetime(60000);      // 连接最大存活时间60秒

流量攻击场景下,数据库主从架构的脆弱性会暴露无遗。攻击流量打到主库后,大量写入操作导致binlog或WAL日志急剧膨胀,从库IO线程忙于拉取日志,SQL线程却因单线程回放而严重滞后。此时必须立即开启从库的并行复制功能,并考虑临时关闭binlog记录部分非核心表的变更。MySQL 8.0的并行复制可以动态调整:

-- 开启基于WRITESET的并行复制,提高从库回放速度
STOP SLAVE SQL_THREAD;
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
SET GLOBAL slave_parallel_workers = 8;
START SLAVE SQL_THREAD;
-- 临时关闭部分表的binlog记录以减少日志量
SET sql_log_bin = 0;
ALTER TABLE temp_data ENGINE=BLACKHOLE;
SET sql_log_bin = 1;

对于核心业务表,流量攻击可能直接导致数据页损坏或索引碎片化。在攻击持续期间,不建议执行全量修复操作,但可以紧急重建被高频访问的索引,并调整InnoDB缓冲池分配策略,将热数据牢牢锁在内存中:

-- 将核心业务表锁定在缓冲池中
ALTER TABLE orders CACHE;
-- 紧急调整缓冲池实例数,减少锁争用
SET GLOBAL innodb_buffer_pool_instances = 8;
-- 关闭自适应哈希索引,避免攻击流量导致哈希冲突
SET GLOBAL innodb_adaptive_hash_index = OFF;

流量攻击往往伴随着数据勒索或拖库风险。在应急响应中,必须立即开启数据库的详细审计日志,记录所有查询操作以便事后溯源。MySQL的general_log虽然性能开销大,但在攻击期间开启几分钟足以获取关键证据:

-- 临时开启通用查询日志
SET GLOBAL general_log = 'ON';
SET GLOBAL log_output = 'TABLE';
-- 5分钟后关闭,避免日志表过大
SET GLOBAL general_log = 'OFF';
-- 从日志表中快速提取高频来源IP
SELECT user_host, COUNT(*) as cnt 
FROM mysql.general_log 
GROUP BY user_host 
ORDER BY cnt DESC LIMIT 10;

PostgreSQL的审计可以通过pgAudit扩展实现更精细的控制,只记录特定表的操作而不过度影响性能:

-- 设置只审计核心表的SELECT操作
ALTER SYSTEM SET pgaudit.log = 'READ';
ALTER SYSTEM SET pgaudit.log_relation = 'ON';
ALTER SYSTEM SET pgaudit.log_catalog = 'OFF';
SELECT pg_reload_conf();

在极端情况下,如果攻击流量已经导致数据库无法响应任何管理命令,必须走操作系统层面的强制恢复路径。对于Linux服务器,可以通过cgroup临时限制mysqld或postgres进程的CPU和内存使用,防止数据库进程被OOM Killer误杀:

# 创建临时cgroup限制数据库进程资源
cgcreate -g cpu,memory:/db_limit
cgset -r cpu.cfs_quota_us=50000 /db_limit
cgset -r memory.limit_in_bytes=8G /db_limit
cgclassify -g cpu,memory:/db_limit $(pidof mysqld)

流量攻击平息后,数据库往往留下大量后遗症:连接池泄漏、未提交的事务、锁等待堆积、临时表空间膨胀。此时需要系统性地清理战场。首先检查并回滚长时间未提交的事务:

-- MySQL中查找运行超过30秒的事务
SELECT * FROM information_schema.innodb_trx 
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 30;
-- 强制回滚指定事务(需先kill对应连接)
KILL CONNECTION 线程ID;

然后收缩临时表空间并重建部分失效索引。对于MySQL 8.0,可以在线重建索引而不阻塞业务:

-- 在线重建索引,消除攻击期间产生的碎片
ALTER TABLE large_table ALGORITHM=INPLACE, LOCK=NONE, 
  REBUILD INDEX idx_create_time;

最后,根据攻击期间收集的慢查询和审计日志,优化被攻击利用的SQL语句。通常攻击者会利用未使用索引的排序操作、多表笛卡尔积连接、或者深度分页查询来消耗数据库资源。针对这些弱点,需要添加覆盖索引、改写查询逻辑、引入应用层缓存或限制分页深度。例如,对于深度分页攻击,可以直接在数据库层面限制OFFSET最大值:

-- 在应用账号层面限制单次查询返回行数
ALTER USER 'app_user'@'%' WITH MAX_QUERIES_PER_HOUR 5000;
-- 在查询中强制使用LIMIT并限制范围
SELECT * FROM orders WHERE create_time > '2024-01-01' 
  ORDER BY id LIMIT 100;

构建长效防护机制时,需要将数据库安全规则前置到接入层。在Nginx或API网关层面配置针对数据库查询接口的速率限制,同时将数据库账号权限最小化,确保应用账号只拥有必要的SELECT、INSERT权限,禁止DROP、ALTER等DDL操作。定期使用pt-query-digest或pgbadger分析慢查询日志,提前发现潜在的性能瓶颈和攻击面。

数据库安全防护的最终形态是形成“边界清洗-代理过滤-内核限流-架构容灾”的四层防御体系。流量攻击无法完全杜绝,但通过上述应急方案和常态化优化,可以将攻击影响控制在分钟级恢复范围内,保障核心数据资产的安全。