流量攻击,尤其是针对应用层的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分析慢查询日志,提前发现潜在的性能瓶颈和攻击面。
数据库安全防护的最终形态是形成“边界清洗-代理过滤-内核限流-架构容灾”的四层防御体系。流量攻击无法完全杜绝,但通过上述应急方案和常态化优化,可以将攻击影响控制在分钟级恢复范围内,保障核心数据资产的安全。
