千万级记录的Discuz论坛导致MySQL CPU 100%的优化笔记

发现此主机运行了几个 Discuz 的论坛程序, Discuz论坛的好几个表也存在着这个问题。于是顺手一并解决,cpu占用再次降下来了。
  前几天,一位朋友通过这篇文章找到了我,说他就是运行最新的 discuz 版本,MySQL 占用 CPU 100%,导致系统假死,每天都要重启好几次,花了一个多月的时间一直没有解决,希望我帮忙一下。经过检查,他的这个论坛最重要的几个表中,目前 cdb_members 表,有记录 6.2 万;cdb_threads 表,有记录 11万;cdb_posts表,有记录 1740 万;所有数据表的记录加起来,超过 2000 万;数据库的大小超过 1GB。经过半天的调试,总算完成了 discuz 论坛优化,于是将其解决经过记录在这篇文章中。

  2007年3月我发现 discuz 论坛的数据库结构设计有一些疏忽,有许多查询子句的条件比较,都没有建立 Index 索引。当时我所检查的那个数据表,记录只有几千条,因此对 CPU 负荷不大。现在这个数据库表,上千万的记录检索,可以想象,如果数据表结构设计不规范,没有提供索引,所耗费的时间是一个恐怖的数字。有关 MySQL 建立索引的重要性,可以参见我的这篇文章底部的说明

  为了调试方便,我从 dizcus 的官网下载了其最新的 Dizcus! 5.5.0 论坛程序.

  我首先检查了 my.ini 的参数配置,一切正常。进入 MySQL 的命令行,调用 show processlist 语句,查找负荷最重的 SQL 语句,结合 Discuz 论坛的源码,发现有以下语句导致 CPU 上升:

代码如下:

mysql> show processlist;
+-----+------+----------------+---------+---------+------+------------+---------
-----------------------------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info

+-----+------+----------------+---------+---------+------+------------+---------
-----------------------------------------------------------------+
| 363 | root | localhost:1393 | history | Query | 0 | statistics | SELECT C
OUNT(*) FROM cdb_pms WHERE msgfromid=11212 AND folder='outbox' |
+-----+------+----------------+---------+---------+------+------------+---------

检查 cdb_pms 表的结构:


代码如下:

mysql> show columns from cdb_pms;
+-----------+------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------------+------+-----+---------+----------------+
| pmid | int(10) unsigned | NO | PRI | NULL | auto_increment |
| msgfrom | varchar(15) | NO | | | |
| msgfromid | mediumint(8) unsigned | NO | MUL | 0 | |
| msgtoid | mediumint(8) unsigned | NO | MUL | 0 | |
| folder | enum('inbox','outbox') | NO | | inbox | |
| new | tinyint(1) | NO | | 0 | |
| subject | varchar(75) | NO | | | |
| dateline | int(10) unsigned | NO | | 0 | |
| message | text | NO | | | |
| delstatus | tinyint(1) unsigned | NO | | 0 | |
+-----------+------------------------+------+-----+---------+----------------+
10 rows in set (0.00 sec)

这条语句: WHERE msgfromid=11212 AND folder='outbox',我们看到,在 cdb_pms 表中,msgfromid 字段已经建立了索引,但是,folder 字段并没有。目前这个表已经有记录 7823 条。显然,这会对查询造成一定影响。于是为其建立索引:


代码如下:

mysql> ALTER TABLE `cdb_pms` ADD INDEX ( `folder` );
Query OK, 7823 rows affected (1.05 sec)
Records: 7823 Duplicates: 0 Warnings: 0

继续检查:


代码如下:

mysql> show processlist;
+------+------+----------------+---------+---------+------+------------+--------
--------------------------------------------------------------------------------
--------------+
| Id | User | Host | db | Command | Time | State | Info

|
+------+------+----------------+---------+---------+------+------------+--------
--------------------------------------------------------------------------------
--------------+

| 1583 | root | localhost:2616 | history | Query | 0 | statistics | SELECT
t.tid, t.closed, f.*, ff.* , f.fid AS fid
FROM cdb_threads t
INNER JOIN cdb_forums f |
+------+------+----------------+---------+---------+------+------------+--------
--------------------------------------------------------------------------------
--------------+
1 rows in set (0.00 sec)

这条 SQL 语句是针对最重要的数据表 cdb_threads 进行操作的,由于 show processlist 没有将这条 SQL 语句全部显示完全,经对比 Discuz 论坛的源码,此SQL语句的原型位于 common.inc.php 的 Line 283,内容如下:


代码如下:

$query = $db->query("SELECT t.tid, t.closed,".(defined('SQL_ADD_THREAD') ?
SQL_ADD_THREAD : '')." f.*, ff.* $accessadd1 $modadd1, f.fid AS fid
FROM {$tablepre}threads t
INNER JOIN {$tablepre}forums f ON f.fid=t.fid
LEFT JOIN {$tablepre}forumfields ff ON ff.fid=f.fid $accessadd2 $modadd2
WHERE t.tid='$tid'".($auditstatuson ? '' : " AND t.displayorder>=0")." LIMIT 1");

经检查,数据表 cdb_threads, 并没有针对 displayorder 字段建立索引。在 discuz 论坛中,displayorder字段多次参与了 Where 子句的比较。于是为其建立索引:


代码如下:

mysql> ALTER TABLE `cdb_threads` ADD INDEX ( `displayorder` );
Query OK, 110330 rows affected (2.36 sec)
Records: 110330 Duplicates: 0 Warnings: 0

此时 cpu 已经轻微下降了一部分。

继续检查,发现 下面这条 discuz 的 SQL 语句,也导致负荷增加,这条语句位于 rss.php 程序中的第 142 行。


代码如下:

$query = $db->query("SELECT t.tid, t.readperm, t.price, t.author, t.dateline, t.subject, p.message
FROM {$tablepre}threads t
LEFT JOIN {$tablepre}posts p ON p.tid=t.tid AND p.first=1
WHERE t.fid='$fid' AND t.displayorder>=0
ORDER BY t.dateline DESC LIMIT $num");

在这个 Order by 子句中,用到了 cdb_threads 表中的 dataline 字段。这个字段是用来存储 unixtime 的时间戳,在整个论坛程序中,大部分时候数据的排序也是基于这个字段,竟然没有建立索引。于是加上:


代码如下:

mysql> ALTER TABLE `cdb_threads` ADD INDEX ( `dateline` );
Query OK, 110330 rows affected (12.27 sec)
Records: 110330 Duplicates: 0 Warnings: 0

查找占用 CPU 高负茶的 SQL 语句,是一件麻烦而又枯燥的事,需要一条一条排除、分析。后面的工作,都是依此类推,经过检查,共查出有八处地方,需要增加索引,如果你也碰到了 discuz 5.5.0 论坛导致 cpu 占用 100% 的情况,可以直接将下列语句复制过去,在 mysql 的命令行下执行即可:


代码如下:

ALTER TABLE `cdb_pms` ADD INDEX ( `folder` );
ALTER TABLE `cdb_threads` ADD INDEX ( `displayorder` );
ALTER TABLE `cdb_threads` ADD INDEX ( `dateline` );
ALTER TABLE `cdb_threads` ADD INDEX ( `closed` );
ALTER TABLE `cdb_threadsmod` ADD INDEX ( `dateline` );
ALTER TABLE `cdb_sessions` ADD INDEX ( `invisible` );
ALTER TABLE `cdb_forums` ADD INDEX ( `type` );
ALTER TABLE `cdb_forums` ADD INDEX ( `displayorder` );

注意:“cdb_” 是 discuz 论坛的默认数据表前缀。如果你的表名前缀不是 “cdb_”,则应该改成你对应的表名。例如:my_threads, my_pms 等等。

  完成这些结构的优化之后,整个系统的 CPU 负荷在 10%~20%左右震荡,问题解决。

  我很奇怪,设计数据库结构,是一个数据库开发人员的基本功,discuz 论坛好歹也是一个发展了有六七年的论坛了,为何数据库结构设计得如此糟糕?我想也许有如下三个原因:

  • 数据库开发人员设计时本身的疏忽
  • 故意留下的缺陷,当普通论坛没有上数量级的记录时,不会感觉到这个问题,当数据量增大(例如千万级),此问题突现,以便针对用户提供个性服务收取服务费.呵呵,估且以最大的恶意来猜测此事,玩笑而已,不必当真。:) 
  • 另一个可能就是用户的论坛是从低版本升级而来,程序升了级,但数据结构也许没有做相应的更新

附1: 补充笔记 2007-07-09

  今天查看网站日志的 reffer, 发现在 discuz 的官方论坛上,有人就此文引起了一些争论: http://www.discuz.net/thread-673887-1-1.html。discuz 的管理员和管理员有如下言论:

引用自 cnteacher:

恰恰相反,discuz 的优化措施和数据库的索引是按照大规模论坛设计的。

TO 一楼:数据库结构的设计都是按照程序应用来进行的,使用任何非Discuz! 标准版本以外的代码和程序,或者变更标准数据结构,均可能遇到不可预知的各种问题。

引用自 童虎:

你们可以看看xxxxx, xxxx之类的比较大型的网站,这种网站使用dz论坛都没有问题,说明dz标准程序是没有问题,出现楼主说的情况,多半属于服务器或者安装一些插件造成的

  显然将问题推给插件的原因是不正确的.举个简单的例子:在最新的 discuz 5.5.0 forumdisplay.php 第183 行,有如下语句:

$query = $db->query("SELECT uid, groupid, username, invisible,
lastactivity, action FROM {$tablepre}sessions
WHERE $guestwhere fid='$fid' AND invisible=0");
  这里的 invisible 并没有建立索引。本文中有评论认为 session 表是内存表, 速度会很快。理论是如此。不过我在 show processlist 中,观察到上面这条语句占用了大量 CPU, 所以也将其一并加上了 index。cdb_threads 中的 closed 等字段, 也多次参与 where 运算, 也没有建立索引。这些运算的语句, 是 discuz 自己的程序中的。

附2: 补充笔记 2007-11-11

  自从这篇笔记发表以来,在我的这篇文章的评论、以及我的联系消息中,就经常收到许多下面两种类型的评论和邮件:一、许多技术人员批评我胡说八道、Dizcus 论坛不需要做优化或者不能乱建索引的;二、许多使用Dizcus 的站长找我“冰天雪地裸体跪求”解决他们的 CPU 占用 100% 的问题。

  一、关于 MySQL 数据库优化技术上的争论,我的观点再次声明如下:

  1. 技术上的争论是可以放开了讨论的。而我的水平也确实只是半瓶水,对数据库的理论知识也只懂这么点,牛牛们的批评,我虚心接心,非常感谢。但是,评论里的批评不要上升到人身攻击,否则,我的地盘我作主,直接删除。

  2. 数据库的优化,要涉及到的方方面面很多。关说理论是没有用的,得靠事实说话。一个千万级数据库的实例优化说明不了问题,两个千万级的数据库优化也许还说明不了问题,但我相信,三个、四个、五个总是可以说明问题的,--截止到 2007.11.09,我已经帮助朋友优化过五个记录数超过 1000 万的 discuz 论坛了。我想事实胜于雄辩:优化之前,cpu 都是 100%;优化之后,cpu 降到 30%~40% 左右。没错,做 ADD INDEX 会增加数据库 INSERT/UPDATE 时的开销,但别忘了论坛最主要的操作,是 SELECT 查询。

  二、关于找我帮忙解决数据库优化的评论和邮件,答复如下:

  1. 数据库的优化,不同的版本有不同的实际情况,优化一个 database,短则三两小时,慢则半天一天。请大家理解这个中年老男人养家的压力,我的精力有限,不可能一一帮到。
  2. 对于没有收入的个人网站,我可以在周六周日的空余时间内帮忙。请事先与我联系好。
  3. 对于有收入的网站,嗯嗯,自觉点,请带价格与我联系,或者直接安排美女请我吃饭,否则免谈。:) 请不要来信问“优化我们这个论坛你要多少费用?”这样没营养的话,而是直接说“帮我们优化 XXXX 论坛, XXXX RMB 可以不?”,我觉得合适就做。大家都很忙,我的时间很值钱,你要我自己报价,我怕吓着你。
  4. 请通过 http://www.xiaohui.com/support/ 与我联系。不要在评论里留个 QQ 号然后要我加你,我不会时时盯着评论看。

附3: 补充笔记 2007-11-17: 关于装有首页四格插件的 dz 论坛导致 MySQL 占用 大量CPU 的分析

  今天手机巴士的站长( http://bbs.sj84.com )找到我,他的基于 Discuz 的论坛,也存在 CPU 占用 100% 的问题,服务器从 Win 2003 换到 CentOS,内存 2G, CPU 1.86G, 数据:cdb_threads 4 万,cdb_posts 96 万,cdb_members 35 万,已经按我上面文章所说的优化过索引。按说这个配置足够运行论坛了,但问题一直得不到解决。

  经过调试,将慢查询的结果 dump 到 /usr/local/mysql/var/localhost-slow.log,运行 /usr/local/mysql/bin/mysqldumpslow /usr/local/mysql/var/localhost-slow.log 查看,结合 show processlist 命令,发现慢查询集中在下列语句:

SELECT t.*, f.name FROM cdb_threads t, cdb_forums f WHERE
t.fid<>'S'
AND f.fid=t.fid
AND f.fid NOT IN (N,N,N,N)
AND t.closed NOT LIKE 'S'
AND t.replies !=N
AND t.displayorder>=N
ORDER BY t.views DESC LIMIT N, N
  然而搜索 Dizcus 论坛的源码,并没有找到这行代码。怀疑是插件的原因。经查,论坛装了首页四格的插件,这行语句位于 include/toplist.php 中: 仔细检查这行代码,发现存在许多性能或语法规范上的问题:

  1. AND t.closed NOT LIKE 'S':t.closed 是数值字段,不应该用 LIKE 'S' 的形式参与比较。 
  2. ORDER BY t.views: t.views 在 dizcus 的原始数据表中,是没有做索引的。
  3. SELECT t.*: 这种写法,是不被推荐的。如果要选择某个表内的所有字段,最好是按实全部写出来,例如:select t.aa, t.bb, t.cc, t.dd, ...
  4. WHERE t.fid <> 'S': t.fid 是数值型字段,不应该写成 字符比较的形式。这个对性能影响不大,是个编程规范的问题。
  5. ....

  toplist.php 的其他三条 sql 语句,都存在这些问题。如果要针对他的 sql 语句去优化 MySQL 结构,会带来不良的后果;如果直接改他的 toplist.php 程序,如果站长以后升级 toplist.php 又怕带来不兼容问题。于是我建议他干脆关闭首页四格插件。

  关闭首页四格插件之后,CPU 降到 18% 左右震荡,表现非常良好。

  如果是我来写首页四格的程序,我不会采用这种方案,我会用定时15分钟或30分钟查询一次数据库,将结果写入 TXT 文件或临时表,然后程序再从中读取,效率会高许多。

  结论:

  1. 如果装了插件的论坛碰到 CPU 高负荷时,建议关掉插件再评估性能。
  2. 慎装第三方插件。没事不要乱插。:)

附4:补充笔记 2008-06-10:这篇文章,重要的是分析过程,而不是进行修正的那段代码


  最近有几位在评论中留言,以及给我 EMAIL,说到将我在文中给出的 那8行 ALTER TABLE 代码,在他的出现 CPU 100% 的 dz 论坛上,用了之后没有效果。

  我的解释如下:这段代码,不能保证在 dz 的所有版本下通用。具体问题,要具体分析。这段代码,是我在 Dizcus! 5.5.0 的版本的基本下进行分析得出的校正结果。其他的版本,不敢保证。

  这篇文章的重点,并不是作为结果的这段代码,而是如何得出这个结果的分析过程。知道了原理,你自己一样可以分析。

(0)

相关推荐

  • 由于mysql运行权限导致无法找到 Discuz! 论坛数据表! 的解决方法

    无法找到 Discuz! 论坛数据表! 在5.5升级到6.0时,上传完后,运行http://*****.com/upgrade9.php 时出下下列问题: 无法找到 Discuz! 论坛数据表! 请修改 config.inc.php 当中关于数据库的设置,然后上传到论坛目录,重新开始升级 config.inc.php 这个文件我用的是以前的,应该没有问题的. 解决方法:刷新N次,就好了.真是不明白为什么. 发现了,原理mysql的权限是普通用户权限,我们可以先将权限设为系统帐户,问题就可以解决了

  • MySQL优化配置文件my.ini(discuz论坛)

    在Apache, PHP, MySQL的体系架构中,MySQL对于性能的影响最大,也是关键的核心部分.对于Discuz!论坛程序也是如此,MySQL的设置是否合理优化,直接影响到论坛的速度和承载量!同时,MySQL也是优化难度最大的一个部分,不但需要理解一些MySQL专业知识,同时还需要长时间的观察统计并且根据经验进行判断,然后设置合理的参数. 下面我们了解一下MySQL优化的一些基础,MySQL的优化我分为两个部分,一是服务器物理硬件的优化,二是MySQL自身(my.cnf)的优化. 一.服务

  • 我已装了Mysql但,安装DZ论坛时,为什么提示没有安装Mysql?

    这是PHP没有安装好造成的, 您可以这样做: 删除c:\windows\php.ini及c:\windows\system32\libmysql.dll后,重启服务器, 然后登陆7i24.com在主控端下载的地方,必要软件下载中,下载星外自动PHP5安装包来安装就可以解决.

  • MySQL针对Discuz论坛程序的基本优化教程

    过了这么久,discuz论坛的问题还是困扰着很多网友,其实从各论坛里看到的问题总结出来,很关键的一点都是因为没有将数据表引擎转成InnoDB导致的,discuz在并发稍微高一点的环境下就表现的非常糟糕,产生大量的锁等待,这时候如果把数据表引擎改成InnoDB的话,我相信会好很多.这次就写个扫盲贴吧. 1. 启用innodb引擎,并配置相关参数 #skip-innodb innodb_additional_mem_pool_size = 16M #一般16M也够了,可以适当调整下 innodb_b

  • 千万级记录的Discuz论坛导致MySQL CPU 100%的优化笔记

    发现此主机运行了几个 Discuz 的论坛程序, Discuz论坛的好几个表也存在着这个问题.于是顺手一并解决,cpu占用再次降下来了. 前几天,一位朋友通过这篇文章找到了我,说他就是运行最新的 discuz 版本,MySQL 占用 CPU 100%,导致系统假死,每天都要重启好几次,花了一个多月的时间一直没有解决,希望我帮忙一下.经过检查,他的这个论坛最重要的几个表中,目前 cdb_members 表,有记录 6.2 万:cdb_threads 表,有记录 11万:cdb_posts表,有记录

  • Mysql CPU占用高的问题解决方法小结

    通过以前对mysql的操作经验,先将mysql的配置问题排除了,查看msyql是否运行正常,通过查看mysql data目录里面的*.err文件(将扩展名改为.txt)记事本查看即可.如果过大不建议用记事本了,容易死掉,可以用editplus等工具 简单的分为下面几个步骤来解决这个问题: 1.mysql运行正常,也有可能是同步设置问题导致 2.如果mysql运行正常,那就是php的一些sql语句导致问题发现,用root用户进入mysql管理mysql -u root -p输入密码mysql:sh

  • MySQL 百万级分页优化(Mysql千万级快速分页)

    以下分享一点我的经验 一般刚开始学SQL的时候,会这样写 复制代码 代码如下: SELECT * FROM table ORDER BY id LIMIT 1000, 10; 但在数据达到百万级的时候,这样写会慢死 复制代码 代码如下: SELECT * FROM table ORDER BY id LIMIT 1000000, 10; 也许耗费几十秒 网上很多优化的方法是这样的 复制代码 代码如下: SELECT * FROM table WHERE id >= (SELECT id FROM

  • MySQL单表千万级数据处理的思路分享

    项目背景 在处理过程中,今天上午需要更新A字段,下午爬虫组完成了规格书或图片的爬取又需要更新图片和规格书字段,由于单表千万级深度翻页会导致处理速度越来越慢. select a,b,c from db.tb limit 10000 offset 9000000 但是时间是有限的,是否有更好的方法去解决这种问题呢? 改进思路 是否有可以不需要深度翻页也可以进行数据更新的凭据? 是的,利用自增id列 观察数据特征 此单表有自增id列且为主键,根据索引列查询数据和更新数据是最理想的途径. select

  • Mysql limit 优化,百万至千万级快速分页 复合索引的引用并应用于轻量级框架

    MySql 这个数据库绝对是适合dba级的高手去玩的,一般做一点1万篇新闻的小型系统怎么写都可以,用xx框架可以实现快速开发.可是数据量到了10万,百万至千万,他的性能还能那么高吗?一点小小的失误,可能造成整个系统的改写,甚至更本系统无法正常运行!好了,不那么多废话了.用事实说话,看例子: 数据表 collect ( id, title ,info ,vtype) 就这4个字段,其中 title 用定长,info 用text, id 是逐渐,vtype是tinyint,vtype是索引.这是一个

  • 如何优化Mysql千万级快速分页

    看例子: 数 据表 collect ( id, title ,info ,vtype) 就这4个字段,其中 title 用定长,info 用text, id 是逐渐,vtype是tinyint,vtype是索引.这是一个基本的新闻系统的简单模型.现在往里面填充数据,填充10万篇新闻. 最后collect 为 10万条记录,数据库表占用硬盘1.6G. OK ,看下面这条sql语句: select id,title from collect limit 1000,10; 很快:基本上0.01秒就OK

  • MySQL如何快速的创建千万级测试数据

    备注: 此文章的数据量在100W,如果想要千万级,调大数量即可,但是不要大量使用rand() 或者uuid() 会导致性能下降 背景 在进行查询操作的性能测试或者sql优化时,我们经常需要在线下环境构建大量的基础数据供我们测试,模拟线上的真实环境. 废话,总不能让我去线上去测试吧,会被DBA砍死的 创建测试数据的方式 1. 编写代码,通过代码批量插库(本人使用过,步骤太繁琐,性能不高,不推荐) 2. 编写存储过程和函数执行(本文实现方式1) 3. 临时数据表方式执行 (本文实现方式2,强烈推荐该

  • .Net Core导入千万级数据至Mysql数据库的实现方法

    ​最近在工作中,涉及到一个数据迁移功能,从一个txt文本文件导入到MySQL功能. 数据迁移,在互联网企业可以说经常碰到,而且涉及到千万级.亿级的数据量是很常见的.大数据量迁移,这里面就涉及到一个问题:高性能的插入数据. 今天我们就来谈谈MySQL怎么高性能插入千万级的数据. 我们一起对比以下几种实现方法: 前期准备 订单测试表 CREATE TABLE `trade` ( `id` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci',

随机推荐