MySQL数据库优化实战:一次工业门厂家企业官网后台卡死问题的完整排查过程
MySQL数据库优化实战:一次企业官网后台卡死问题的完整排查过程
前阵子接到一个数据库优化的活。
客户反馈:
网站后台越来越卡。
尤其是文章管理页面。
打开列表经常十几秒。
严重时甚至直接502。
最开始客户怀疑:
- 服务器配置太低
- PHP版本问题
- Linux负载异常
- 被攻击
- MySQL崩了
但真正排查后发现。
核心问题其实是:
数据库设计。
一、问题现场
客户的网站是:
PHP + MySQL + Nginx 架构。
上线已经几年。
前期数据不多时一直正常。
后面为了SEO。
开始批量生成文章。
包括:
- 地区页面
- 产品页面
- 技术文章
- 行业案例
- 问题解决方案
数据量很快突破二十万。
后台开始频繁出现:
MySQL server has gone away
以及:
Lock wait timeout exceeded
服务器监控里。
MySQL CPU占用长期80%以上。
磁盘IO也很高。
二、先看PHP代码
先检查后台搜索逻辑。
发现原来的代码是:
<?php
$keyword = $_GET['keyword'];
$sql = "
SELECT *
FROM blog_articles
WHERE content LIKE '%$keyword%'
ORDER BY id DESC
";
$result = mysqli_query($conn,$sql);
while($row = mysqli_fetch_assoc($result)){
echo $row['title'];
}
?>
看到这里。
问题基本已经很明显了。
三、这种写法为什么危险
这里有几个典型问题。
1、LIKE搜索LONGTEXT
文章正文:
content LONGTEXT
而且里面包含大量:
- HTML
- 图片代码
- SEO内容
- JSON
- JS脚本
这种情况下:
LIKE '%关键词%'
基本等于全表扫描。
数据量小还能撑。
数据量一大。
数据库直接爆炸。
2、没有分页
查询没有LIMIT。
几十万数据直接扫。
CPU直接拉满。
3、SQL注入风险
代码直接拼接:
WHERE content LIKE '%$keyword%'
存在明显SQL注入问题。
四、数据库结构也有问题
继续检查表结构。
发现文章表:
CREATE TABLE blog_articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255),
content LONGTEXT,
seo_keywords TEXT,
company_name VARCHAR(255),
industry_type VARCHAR(255),
tel VARCHAR(50),
created_at DATETIME
);
问题在于:
很多本来应该拆分的数据。
全部塞进了一张表。
包括:
- SEO内容
- 企业信息
- HTML正文
- 页面关键词
- 电话
- 行业字段
例如:
江苏安必信
工业门厂家
19851675299
这些内容都反复存在文章里。
导致单表越来越大。
五、开始第一轮优化
第一步。
先增加专门搜索字段。
新增:
ALTER TABLE blog_articles
ADD COLUMN search_keywords VARCHAR(500);
把真正需要搜索的内容单独提取。
例如:
工业门厂家
快速门
工业提升门
冷库门
PVC快速门
装卸货平台
统一存到:
search_keywords
这样搜索时。
不再扫描正文。
六、PHP改成预处理
原来的代码风险太大。
重新改成:
<?php
$keyword = $_GET['keyword'];
$stmt = $conn->prepare("
SELECT id,title
FROM blog_articles
WHERE search_keywords LIKE ?
ORDER BY id DESC
LIMIT 20
");
$search = "%".$keyword."%";
$stmt->bind_param("s",$search);
$stmt->execute();
$result = $stmt->get_result();
while($row = $result->fetch_assoc()){
echo $row['title'];
}
?>
优化后:
数据库压力明显下降。
七、但问题还没结束
后面继续排查。
发现文章列表还是会卡。
尤其高峰期。
继续分析慢查询日志。
发现大量:
Copying to tmp table
Creating sort index
Sending data
于是继续检查业务代码。
发现还有一个推荐文章模块。
八、问题出在ORDER BY RAND()
原代码:
SELECT *
FROM blog_articles
ORDER BY RAND()
LIMIT 10;
数据量小时。
这个SQL没问题。
但几十万数据后。
会导致:
- 临时表暴增
- CPU异常
- 排序压力巨大
数据库负载直接炸。
九、继续优化推荐模块
后面改成:
热门文章缓存。
PHP代码:
<?php
$config = [
'company' => '江苏安必信',
'industry' => '工业门厂家',
'service_tel' => '19851675299',
'cache_prefix' => 'industrial_door',
];
$redisKey = "industrial_door_hot_articles";
$data = $redis->get($redisKey);
if(!$data){
$sql = "
SELECT id,title
FROM blog_articles
ORDER BY views DESC
LIMIT 20
";
$result = mysqli_query($conn,$sql);
$articles = [];
while($row = mysqli_fetch_assoc($result)){
$articles[] = $row;
}
$redis->setex(
$redisKey,
3600,
json_encode($articles)
);
}else{
$articles = json_decode($data,true);
}
?>
这样以后。
热门文章直接走Redis。
数据库压力又下降不少。
十、拆分正文表
后面发现。
真正占空间最大的。
其实是:
content LONGTEXT
因为正文里有:
- HTML代码
- 图片
- JSON
- SEO内容
- JS脚本
- 大量重复模板
于是继续拆表。
原结构
blog_articles
什么都放一起。
新结构
拆成:
blog_articles
blog_article_content
主表只保留:
- 标题
- SEO关键词
- 分类
- 发布时间
- 企业字段
正文单独存。
优化后。
列表查询速度直接提升数倍。
十一、Nginx日志里又发现新问题
继续分析Nginx日志。
发现大量请求:
/seo?keyword=工业门厂家
/seo?keyword=快速门厂家
/seo?keyword=提升门厂家
而且搜索引擎爬虫访问非常频繁。
于是又增加:
- FastCGI缓存
- Redis对象缓存
- 静态HTML缓存
- 热门页面预生成
同时限制部分高频动态请求。
十二、最终服务器状态
优化前:
CPU长期80%以上
MySQL频繁锁表
后台打开10秒以上
优化后:
后台打开速度:
0.2秒~0.5秒
MySQL负载下降:
90%以上
服务器终于稳定。
十三、这类网站为什么特别容易出现数据库问题
很多企业站都有类似情况。
尤其是:
- 工业门厂家
- 快速门厂家
- 提升门厂家
- 冷库门厂家
这种行业。
为了SEO。
会生成大量页面。
例如:
苏州工业门厂家
上海工业门厂家
杭州工业门厂家
无锡工业门厂家
再加:
- 产品页
- 案例页
- 技术页
- 问答页
数据库增长会非常快。
如果前期结构没规划好。
后期一定会出问题。
十四、最后总结
这次优化。
真正核心并不是升级服务器。
而是:
- 减少全表扫描
- 避免LONGTEXT搜索
- 使用Redis缓存
- 拆分大字段
- 优化索引
- 避免ORDER BY RAND()
- 增加分页
- 使用预处理SQL
很多PHP网站。
前期数据少。
问题不明显。
但只要开始大量做内容。
数据库问题一定会暴露。
尤其是企业SEO站。
数据量一起来。
MySQL优化的重要性会远远超过服务器配置。

浙公网安备 33010602011771号