Files
kefu/im/MySQL修复记录-2026-08-31.md
2026-09-03 08:38:17 +08:00

169 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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):非阻塞文件锁。
本记录、部署脚本和本地副本均未保存登录密码或数据库密码。