# MySQL 与首页请求保护修复记录 **17:42 更新:此前 Nginx 每 IP 和首页总量频率限制已撤回;目前只针对已确认的异常请求直接关闭连接,另有 84 个异常来源临时拦截 15 分钟。数据库索引与 PHP 缓存保留。当前访问与到期边界详见 [网络拥塞处理记录](./网络拥塞处理-2026-08-31.md)。下文 15:32 的限速测试为历史记录,不代表现配置。** 服务器:47.106.181.28。完成时间:2026-08-31 15:32,北京时间。 已直接部署到生产环境。修复范围为 `www.txiaw.com` 的首页请求保护、首页文章列表缓存,以及共享数据库 `xiaxia.gxl_news` 的查询索引。 ## 已完成的修改 ### 1. 数据库索引 已执行: ```sql SET SESSION lock_wait_timeout = 5; ALTER TABLE xiaxia.gxl_news ADD INDEX idx_news_status_addtime (news_status, news_addtime), ALGORITHM=INPLACE, LOCK=NONE; ``` - 执行前检查了现有索引,没有发现前两列为 `(news_status, news_addtime)` 的适用索引。 - 保留原有分类列表等索引,没有删除索引或修改业务记录。 - 先用一致性快照备份目标表,校验 gzip 完整性后才执行 ALTER。 - 在线创建耗时约 **0.49 秒**;明确指定并发读写及原地算法,避免自动回退到需要复制表的操作。 - 新执行计划使用 `idx_news_status_addtime`,不再出现 `Using filesort`。 - 关闭 MySQL 查询缓存后连续测试三次,均返回 100 条,包含客户端启动的耗时分别约 **4.7、4.3、4.0 毫秒**。 - 单次查询会话计数:`Handler_read_key=1`、`Handler_read_prev=99`、`Sort_rows=0`、`Sort_scan=0`。 原故障负载下同一 SQL 的慢日志样本平均为 6.479 秒,每次检查 72,352 行;前后测试处于不同流量条件,不能把全部耗时改善单独归因于索引。 ### 2. PHP 入口保护与 Nginx 首页限速 新增 PHP 文件: ```text /www/wwwroot/www.txiaw.com/Lib/HomeRequestProtection.php ``` 由网站 `index.php` 在加载 ThinkPHP 之前引入。对 `/` 或 `/index.php` 的 GET/HEAD 请求,若同时带 `r` 参数且 User-Agent 为空,则返回 429;PHP 响应附带 `Retry-After: 60`。不依靠客户端提交的 IP 地址,也不读取数据库。 此前 Nginx 同步在请求进入 PHP 之前拦截上述异常特征,并启用了以下首页频率限制(**17:18 已撤回**): - 每个来源 IP:平均 **5 次/秒**,允许 **20 次突发**。 - 该站首页总量:平均 **50 次/秒**,允许 **100 次突发**。 - 超出限制返回 **429**。 - 限速键使用原始请求路径;普通 `/down/...` 等文章路径、静态资源和 POST 请求不在这些首页限速键的范围内。 - 限速配置只被 `www.txiaw.com` / `txiaw.com` 这个虚拟主机引用,不对其他站点启用。 变更文件: ```text /www/wwwroot/www.txiaw.com/index.php /www/server/panel/vhost/nginx/www.txiaw.com.conf /www/server/panel/vhost/nginx/txiaw-home-protection-http.inc /www/server/panel/vhost/nginx/txiaw-home-protection-server.inc ``` 首次部署时 Nginx 修改前后均通过 `nginx -t`,只做了一次平滑重新加载。17:18 撤回频率限制、17:36 启用精确连接关闭规则时也分别检查配置并平滑重载。未重启 MySQL、PHP-FPM 或服务器,未终止业务查询。 ### 3. PHP 首页文章列表缓存 新增: ```text /www/wwwroot/www.txiaw.com/Lib/HomeNewsCache.php ``` 修改首页模板: ```text /www/wwwroot/www.txiaw.com/Tpl/icp/gxl_index.html ``` 该列表仍调用原来的文章查询函数、返回原来的 100 条数据,但增加: - 固定缓存键,URL 的随机参数不参与缓存键。 - **60 秒**有效期。 - 文件锁避免正常并发刷新时重复查询。 - 正在刷新时可使用最近 **300 秒**内的旧值,降低刷新期间的请求排队。 - 冷缓存等待最多约 200 毫秒;缓存设施故障时回退原查询,避免因缓存文件故障直接让页面不可用。新索引和异常特征拦截继续保留,目前不依赖首页频率限制。 - 同目录临时文件加重命名写入,避免读到半份 JSON。 实际缓存位于: ```text /www/wwwroot/www.txiaw.com/Runtime/TxiawHomeNews/news-list-v1.json ``` 只备份并清理了命中这条旧首页查询的 **1 个编译模板缓存文件**,没有清空全站 Runtime、会话或其他业务数据。 ## 验证结果 以下为首次修复的历史验证,当前网络验证见上方更新记录。 已通过以下验证: - PHP 5.6 语法检查。 - 11 个请求保护边界用例,包括正常首页、浏览器请求、文章路径、POST 和证书验证路径。 - 缓存命中、过期刷新、持锁时读取旧值、模拟数据库异常回退、损坏缓存恢复。 - 8 个同时到达的冷缓存请求取得相同数据,数据加载函数只执行 **1 次**。 - 首页加入随机参数仍复用同一个新鲜缓存,实际缓存包含 100 条数据。 - 30 次、最多 4 路并发的短突发 HTTP 检查:**24 次 200、6 次 429**;等待 5 秒后首页恢复 200。 - 首页限速期间,同站文章页和 `www.xxiaw.com` 首页仍为 200。 实际页面检查: | 请求 | 结果 | | --- | --- | | 无 User-Agent 的 `www.txiaw.com/?r=...` | 429 | | 正常 `www.txiaw.com/` | 200 | | `www.txiaw.com/down/184778.html` | 200 | | `www.xxiaw.com/` | 200 | | `m.bchongw.com/` | 301,保留站点原跳转行为 | 15:30 的数据库采样: | 指标 | 故障期间 | 修复后采样 | | --- | --- | --- | | Threads_running | 79 | 1 | | Threads_connected | 80 | 7 | | 慢查询增长 | 约 12.4 次/秒 | 5 秒采样内 0 次 | | MySQL CPU | 约 136%(3 秒样本) | 约 18.33%(3 秒样本) | | 系统 1 分钟负载 | 修改前约 86.31 | 15:32 约 1.51 | 这些是短时采样,不是长期服务承诺。5/15 分钟负载仍包含此前故障窗口,会滞后回落。 ## 剩余压力与边界 异常请求尚未停止:验证时最近 3,000 条、以及后续最近 1,000 条该站访问记录均返回 429,说明仍在持续拦截。15:32 的两秒整机 CPU 样本空闲约 15%、I/O 等待约 1%;入口处理仍消耗资源。此次已经消除已定位的 MySQL 排序热点并限制首页流量,但没有部署云端 WAF/CDN,也未宣称能够抵御任意规模或任意路径的攻击。 后续可在云侧增加 CC 防护,减轻源站的连接与 TLS 处理压力。若站点以后接入代理/CDN,需要同步核对可信真实 IP 配置和合法流量,再调整当前每 IP 阈值。 ## 备份与回滚 服务器备份目录(权限 0700): ```text /www/backup/txiaw-mysql-fix-20260831/ ``` 主要文件: - `originals/original-index.php`:原 PHP 入口。 - `originals/original-www.txiaw.com.conf`:原 Nginx 站点配置。 - `originals/original-gxl_index.html`:原首页模板。 - `compiled-home-before/`:本次清理的首页编译模板备份。 - `gxl_news-before.sql.gz`:完整目标表一致性快照,压缩后 **15,245,809 字节**,已做 gzip 完整性校验。 - `gxl_news-schema-before.txt`:修改前表结构和索引。 - `index-result.json`、`cache-result.json`、`verification-result.json`、`final-summary.json`:实际执行与验证记录。 - `stage/`:此次部署的源码与测试脚本。 回滚时先核对文件是否又被别人修改,避免覆盖后续变更。建议按所需范围单独回滚: 1. **仅撤回 PHP 缓存**:恢复原首页模板,保留其当前属主和权限;只重新生成该首页的编译模板缓存。缓存辅助 PHP 文件可先保留,避免在途请求的已编译模板调用失效。无需导入数据库备份。 2. **撤回请求保护**:恢复原 PHP 入口和原 Nginx 站点配置,先执行 `nginx -t`,成功后再 `nginx -s reload`。两个 `.inc` 文件可保留为未引用文件,不必急于删除。撤回后当前刷请求会再次进入 PHP,应先有替代保护。 3. **确需撤回新索引**:确认没有其他新依赖后,使用短元数据锁等待时间,单独删除 `idx_news_status_addtime`;不需要删除旧索引或恢复整张表。 不要为了撤销索引而直接导入整表备份,这会有覆盖修复后新增业务数据的风险。 ## 参考资料 - [Nginx 请求速率限制](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html):请求键、突发限制及拒绝状态码。 - [MySQL InnoDB 在线 DDL](https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl.html):`ALGORITHM` / `LOCK` 约束的用途;本次同时以服务器实际 MySQL 5.6.50 的执行结果验证支持情况。 - [PHP flock](https://www.php.net/manual/en/function.flock.php):非阻塞文件锁。 本记录、部署脚本和本地副本均未保存登录密码或数据库密码。